mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-21 00:00:49 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae0086f588 | ||
|
|
ac63b981ad | ||
|
|
04099ae2a2 | ||
|
|
c695a2a408 | ||
|
|
690cb48280 | ||
|
|
f226fed8cd |
@@ -9,7 +9,8 @@ type AgentCreateReq struct {
|
||||
WebUIPort int `json:"webUIPort" validate:"required,min=1,max=65535"`
|
||||
BridgePort int `json:"bridgePort"`
|
||||
AllowedOrigins []string `json:"allowedOrigins"`
|
||||
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent"`
|
||||
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw hermes-agent deepseek-harness octop"`
|
||||
AccessHost string `json:"accessHost"`
|
||||
Model string `json:"model"`
|
||||
AccountID uint `json:"accountId"`
|
||||
Token string `json:"token"`
|
||||
@@ -110,6 +111,7 @@ type AgentBatchOperateResult struct {
|
||||
}
|
||||
|
||||
type AgentItem struct {
|
||||
AccessHost string `json:"accessHost"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Remark string `json:"remark"`
|
||||
|
||||
@@ -26,6 +26,7 @@ type FirewallSubsystemStatus struct {
|
||||
}
|
||||
|
||||
type FirewallLifecycleOperation struct {
|
||||
BackupFile string `json:"backupFile,omitempty" validate:"omitempty,max=255"`
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"`
|
||||
WithDockerRestart bool `json:"withDockerRestart"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
@@ -144,6 +145,11 @@ type MonitorVLLMCurrent struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
}
|
||||
|
||||
type MonitorVLLMCurrentData struct {
|
||||
model.MonitorVLLM
|
||||
Metrics map[string]json.RawMessage `json:"metrics,omitempty"`
|
||||
}
|
||||
|
||||
type MonitorVLLMClean struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -86,7 +86,9 @@ type MonitorVLLM struct {
|
||||
RawMetrics string `json:"-"`
|
||||
HistogramDeltas string `json:"-"`
|
||||
IntervalSeconds float64 `json:"intervalSeconds" gorm:"-"`
|
||||
SampleIntervalSeconds int `json:"-"`
|
||||
RequestDeltas string `json:"-"`
|
||||
CompletedRequests *float64 `json:"completedRequests" gorm:"-"`
|
||||
QueueTime *float64 `json:"queueTime"`
|
||||
AverageGenerationTokens *float64 `json:"averageGenerationTokens"`
|
||||
PrefixCacheHitRate *float64 `json:"prefixCacheHitRate"`
|
||||
|
||||
@@ -16,7 +16,8 @@ type VLLMMonitorRepo struct{}
|
||||
|
||||
type VLLMHistoryPoint struct {
|
||||
model.MonitorVLLM
|
||||
Bucket int64
|
||||
Bucket int64
|
||||
CompletedRequests *float64
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) Create(point *model.MonitorVLLM) error {
|
||||
@@ -77,12 +78,19 @@ func (r *VLLMMonitorRepo) History(ctx context.Context, id uint, start, end time.
|
||||
}
|
||||
db = db.Group("bucket").Order("bucket ASC")
|
||||
} else {
|
||||
columns = append([]string{"id", "created_at", "app_install_id", "status"}, metrics...)
|
||||
columns = append([]string{"id", "created_at", "app_install_id", "status", "sample_interval_seconds"}, metrics...)
|
||||
if group == "" || group == "requests" {
|
||||
columns = append(columns, "histogram_deltas", "request_deltas")
|
||||
}
|
||||
db = db.Order("created_at ASC, id ASC")
|
||||
}
|
||||
if group == "" || group == "overview" {
|
||||
count := "CASE WHEN json_valid(request_deltas) THEN CASE WHEN json_extract(request_deltas, '$.seconds') > 0 THEN ROUND(request_throughput * json_extract(request_deltas, '$.seconds')) END END"
|
||||
if seconds > 0 {
|
||||
count = "SUM(" + count + ")"
|
||||
}
|
||||
columns = append(columns, count+" AS completed_requests")
|
||||
}
|
||||
var points []VLLMHistoryPoint
|
||||
err := db.Select(strings.Join(columns, ", ")).Scan(&points).Error
|
||||
return points, err
|
||||
|
||||
@@ -142,6 +142,9 @@ const (
|
||||
|
||||
func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
agentType := req.AgentType
|
||||
if err := validateNativeAgentCreate(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := checkPortExist(req.WebUIPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -237,6 +240,8 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
}
|
||||
} else if agentType == constant.AppCopaw {
|
||||
dashboardAuth = normalizeAgentDashboardAuth(req.DashboardUsername, req.DashboardPassword)
|
||||
} else if agentType == constant.AppDeepSeekHarness || agentType == constant.AppOctop {
|
||||
dashboardAuth = agentDashboardAuth{Username: req.DashboardUsername, Password: req.DashboardPassword}
|
||||
}
|
||||
|
||||
params := map[string]interface{}{
|
||||
@@ -245,6 +250,12 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
||||
constant.HostIP: "",
|
||||
}
|
||||
setAgentWebUIParams(params, agentType, detail.Version, req.WebUIPort)
|
||||
if agentType == constant.AppDeepSeekHarness {
|
||||
params["HTTPS_ACCESS_HOST"] = req.AccessHost
|
||||
}
|
||||
if agentType == constant.AppOctop {
|
||||
params["OCTOP_LOG_LEVEL"] = "info"
|
||||
}
|
||||
if agentType == constant.AppOpenclaw {
|
||||
if allowedOrigin := firstAllowedOrigin(allowedOrigins); allowedOrigin != "" {
|
||||
params["ALLOWED_ORIGIN"] = allowedOrigin
|
||||
@@ -738,7 +749,7 @@ func requiresAgentAccount(agentType string) bool {
|
||||
}
|
||||
|
||||
func setAgentWebUIParams(params map[string]interface{}, agentType, appVersion string, webUIPort int) {
|
||||
if agentType == constant.AppOpenclaw && isOpenclawHTTPSWindowVersion(appVersion) {
|
||||
if agentType == constant.AppDeepSeekHarness || agentType == constant.AppOpenclaw && isOpenclawHTTPSWindowVersion(appVersion) {
|
||||
params["PANEL_APP_PORT_HTTPS"] = webUIPort
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
)
|
||||
|
||||
var deepSeekUsernamePattern = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)
|
||||
var deepSeekHostLabelPattern = regexp.MustCompile(`^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$`)
|
||||
|
||||
func isDeepSeekAccessHost(value string) bool {
|
||||
if ip := net.ParseIP(value); ip != nil {
|
||||
return ip.To4() != nil && !strings.Contains(value, ":")
|
||||
}
|
||||
if value == "" || len(value) > 253 || strings.Trim(value, "0123456789.") == "" {
|
||||
return false
|
||||
}
|
||||
for _, label := range strings.Split(value, ".") {
|
||||
if !deepSeekHostLabelPattern.MatchString(label) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func validateNativeAgentCreate(req dto.AgentCreateReq) error {
|
||||
if req.AgentType != constant.AppDeepSeekHarness && req.AgentType != constant.AppOctop {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(req.DashboardUsername) == "" || strings.ContainsAny(req.DashboardUsername, "\r\n\x00") {
|
||||
return buserr.WithDetail("ErrInvalidParams", "dashboardUsername", nil)
|
||||
}
|
||||
if strings.ContainsAny(req.DashboardPassword, "\r\n\x00") || req.DashboardPassword != strings.TrimSpace(req.DashboardPassword) {
|
||||
return buserr.WithDetail("ErrInvalidParams", "dashboardPassword", nil)
|
||||
}
|
||||
if req.AgentType == constant.AppDeepSeekHarness {
|
||||
if !isDeepSeekAccessHost(req.AccessHost) {
|
||||
return buserr.WithDetail("ErrInvalidParams", "accessHost", nil)
|
||||
}
|
||||
if !deepSeekUsernamePattern.MatchString(req.DashboardUsername) {
|
||||
return buserr.WithDetail("ErrInvalidParams", "dashboardUsername", nil)
|
||||
}
|
||||
if utf8.RuneCountInString(req.DashboardPassword) < 12 {
|
||||
return buserr.WithDetail("ErrInvalidParams", "dashboardPassword", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
hasLetter, hasDigit := false, false
|
||||
for _, char := range req.DashboardPassword {
|
||||
hasLetter = hasLetter || unicode.IsLetter(char)
|
||||
hasDigit = hasDigit || unicode.IsDigit(char)
|
||||
}
|
||||
if utf8.RuneCountInString(req.DashboardPassword) < 8 || !hasLetter || !hasDigit {
|
||||
return buserr.WithDetail("ErrInvalidParams", "dashboardPassword", nil)
|
||||
}
|
||||
switch strings.ToLower(req.DashboardPassword) {
|
||||
case "password", "password1", "password12", "password123", "12345678", "123456789", "qwerty123",
|
||||
"admin123", "welcome1", "letmein1", "changeme1", "octop123", "abc12345", "iloveyou1":
|
||||
return buserr.WithDetail("ErrInvalidParams", "dashboardPassword", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -415,6 +415,15 @@ func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map
|
||||
} else {
|
||||
item.WebUIPort = appInstall.HttpPort
|
||||
}
|
||||
if agentType == constant.AppDeepSeekHarness {
|
||||
item.WebUIPort = appInstall.HttpsPort
|
||||
if values, err := readAgentEnvMap(appInstall.GetEnvPath()); err == nil {
|
||||
accessHost := values["HTTPS_ACCESS_HOST"]
|
||||
if isDeepSeekAccessHost(accessHost) {
|
||||
item.AccessHost = accessHost
|
||||
}
|
||||
}
|
||||
}
|
||||
item.Path = appInstall.GetPath()
|
||||
item.Status = appInstall.Status
|
||||
item.Message = appInstall.Message
|
||||
@@ -442,7 +451,8 @@ func localizedAgentProviderName(provider string) string {
|
||||
}
|
||||
|
||||
func isAgentAppKey(appKey string) bool {
|
||||
return appKey == constant.AppOpenclaw || appKey == constant.AppCopaw || appKey == constant.AppHermesAgent
|
||||
return appKey == constant.AppOpenclaw || appKey == constant.AppCopaw || appKey == constant.AppHermesAgent ||
|
||||
appKey == constant.AppDeepSeekHarness || appKey == constant.AppOctop
|
||||
}
|
||||
|
||||
func isOpenclawLegacyHTTPVersion(version string) bool {
|
||||
@@ -1641,6 +1651,10 @@ func agentDashboardAuthEnvKeys(agentType string) (string, string, bool) {
|
||||
return hermesDashboardUsernameEnvKey, hermesDashboardPasswordEnvKey, true
|
||||
case constant.AppCopaw:
|
||||
return qwenPawAuthUsernameEnvKey, qwenPawAuthPasswordEnvKey, true
|
||||
case constant.AppDeepSeekHarness:
|
||||
return "DSH_AUTH_USERNAME", "DSH_AUTH_PASSWORD", true
|
||||
case constant.AppOctop:
|
||||
return "OCTOP_ADMIN_USERNAME", "OCTOP_DEFAULT_PASSWORD", true
|
||||
default:
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
@@ -131,6 +131,9 @@ func (s *FirewallService) LoadBaseInfo(chainGroup string) (dto.FirewallSubsystem
|
||||
|
||||
func (s *FirewallService) QueueFirewallOperation(request dto.FirewallLifecycleOperation) (dto.FirewallLifecycleOperationResponse, error) {
|
||||
response := dto.FirewallLifecycleOperationResponse{}
|
||||
if request.BackupFile != "" && request.Operation != string(lifecycle.OperationStart) {
|
||||
return response, fmt.Errorf("backup restoration is only supported when starting firewalld or ufw")
|
||||
}
|
||||
if request.Operation == "disableBanPing" || request.Operation == "enableBanPing" {
|
||||
return response, s.OperateFirewall(request)
|
||||
}
|
||||
@@ -155,6 +158,19 @@ func (s *FirewallService) QueueFirewallOperation(request dto.FirewallLifecycleOp
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
var restoreRequest dto.FirewallRuleCreate
|
||||
if request.BackupFile != "" {
|
||||
if client.Name() != lifecycle.ProviderFirewalld && client.Name() != lifecycle.ProviderUFW {
|
||||
return response, fmt.Errorf("backup restoration is only supported when starting firewalld or ufw")
|
||||
}
|
||||
restoreRequest, err = prepareFirewallRuleCreate(dto.FirewallRuleCreate{BackupFile: request.BackupFile, Initialize: true})
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
if err := validateFirewallCreateBatch(restoreRequest, filter.Provider(client.Name())); err != nil {
|
||||
return response, err
|
||||
}
|
||||
}
|
||||
if (client.Name() != lifecycle.ProviderFirewalld && client.Name() != lifecycle.ProviderUFW) ||
|
||||
(request.Operation != string(lifecycle.OperationStart) && request.Operation != string(lifecycle.OperationStop) &&
|
||||
request.Operation != string(lifecycle.OperationRestart)) {
|
||||
@@ -173,7 +189,26 @@ func (s *FirewallService) QueueFirewallOperation(request dto.FirewallLifecycleOp
|
||||
return response, err
|
||||
}
|
||||
taskItem.AddSubTaskWithOps(name, func(t *task.Task) error {
|
||||
return s.runFirewallLifecycleTask(t, client, request, true)
|
||||
if request.BackupFile != "" {
|
||||
if err := s.checkSelectedProvider(t.TaskCtx, filter.Provider(client.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
prepareStart := func(lifecycle.Client) error {
|
||||
if request.BackupFile != "" {
|
||||
return runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallCreateRulesStep"), func() error {
|
||||
return s.runFirewallCreateRulesTask(t, restoreRequest, filter.Provider(client.Name()))
|
||||
})
|
||||
}
|
||||
return runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallSyncWhitelistStep"), func() error {
|
||||
err := s.SyncPortWhitelist(t.TaskCtx)
|
||||
if err == nil && client.Name() == lifecycle.ProviderFirewalld {
|
||||
err = lifecycleproviders.RemoveFirewalldSSHService()
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
return s.runFirewallLifecycleTask(t, client, request, prepareStart)
|
||||
}, nil, 0, 0)
|
||||
if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil {
|
||||
closeUnstartedFirewallTask(taskItem)
|
||||
@@ -554,29 +589,9 @@ func (s *FirewallService) LoadFirewallNativeDetail(ctx context.Context, request
|
||||
}
|
||||
|
||||
func (s *FirewallService) Create(ctx context.Context, request dto.FirewallRuleCreate) (dto.FirewallRuleCreateResponse, error) {
|
||||
if request.BackupFile != "" {
|
||||
if len(request.Items) != 0 {
|
||||
return dto.FirewallRuleCreateResponse{}, filter.ErrInvalidRule
|
||||
}
|
||||
items, err := readFirewallBackup(request.BackupFile)
|
||||
if err != nil {
|
||||
return dto.FirewallRuleCreateResponse{}, err
|
||||
}
|
||||
for _, item := range items {
|
||||
request.Items = append(request.Items, dto.FirewallRuleCreateItem{Rule: item.FirewallRule, Raw: item.Raw, ParseStatus: item.ParseStatus, SourceKind: constant.FirewallRuleSourceImported})
|
||||
}
|
||||
}
|
||||
|
||||
for index, item := range request.Items {
|
||||
if item.SourceKind != constant.FirewallRuleSourceImported || item.Rule.Scope.Provider != filter.ProviderUFW || item.ParseStatus != filter.ParseStatusPartial || item.Raw == "" {
|
||||
continue
|
||||
}
|
||||
observed := filterufw.ParseRules(item.Rule.Scope, item.Raw)
|
||||
if len(observed) == 1 && observed[0].ParseStatus == filter.ParseStatusSupported {
|
||||
observed[0].Rule.Description = item.Rule.Description
|
||||
request.Items[index].Rule = observed[0].Rule
|
||||
request.Items[index].ParseStatus, request.Items[index].Raw = filter.ParseStatusSupported, ""
|
||||
}
|
||||
request, err := prepareFirewallRuleCreate(request)
|
||||
if err != nil {
|
||||
return dto.FirewallRuleCreateResponse{}, err
|
||||
}
|
||||
|
||||
provider, err := s.selectedProvider(ctx)
|
||||
@@ -609,30 +624,11 @@ func (s *FirewallService) Create(ctx context.Context, request dto.FirewallRuleCr
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.runFirewallLifecycleTask(t, client, dto.FirewallLifecycleOperation{Operation: "start"}, false)
|
||||
return s.runFirewallLifecycleTask(t, client, dto.FirewallLifecycleOperation{Operation: "start"}, nil)
|
||||
}, nil)
|
||||
}
|
||||
ruleState := make(map[string]filter.RuleSet)
|
||||
taskItem.AddSubTaskWithOps(i18n.GetMsgByKey("FirewallCreateRulesStep"), func(t *task.Task) error {
|
||||
firewallRuleMutationMu.Lock()
|
||||
if err := s.checkSelectedProvider(t.TaskCtx, provider); err != nil {
|
||||
firewallRuleMutationMu.Unlock()
|
||||
return err
|
||||
}
|
||||
_, err := s.createRules(t.TaskCtx, request, t, ruleState)
|
||||
firewallRuleMutationMu.Unlock()
|
||||
if !request.Initialize || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
ruleState = nil
|
||||
}
|
||||
whitelistErr := s.syncPortWhitelist(t.TaskCtx, ruleState)
|
||||
if whitelistErr == nil && provider == filter.ProviderFirewalld {
|
||||
whitelistErr = lifecycleproviders.RemoveFirewalldSSHService()
|
||||
}
|
||||
t.LogWithStatus(i18n.GetMsgByKey("FirewallSyncWhitelistStep"), whitelistErr)
|
||||
return errors.Join(err, whitelistErr)
|
||||
return s.runFirewallCreateRulesTask(t, request, provider)
|
||||
}, nil, 0, 0)
|
||||
if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil {
|
||||
taskItem.LogFailedWithErr(taskItem.Name, err)
|
||||
@@ -777,7 +773,11 @@ func (s *FirewallService) restoreFirewallAfterStart(client lifecycle.Client) err
|
||||
return nil
|
||||
}
|
||||
}
|
||||
recordFailure("restore whitelist allowances", s.SyncPortWhitelist(ctx))
|
||||
whitelistErr := s.SyncPortWhitelist(ctx)
|
||||
if whitelistErr == nil && provider == filter.ProviderFirewalld {
|
||||
whitelistErr = lifecycleproviders.RemoveFirewalldSSHService()
|
||||
}
|
||||
recordFailure("restore whitelist allowances", whitelistErr)
|
||||
return errors.Join(recoveryErrors...)
|
||||
}
|
||||
|
||||
@@ -805,17 +805,9 @@ func (s *FirewallService) restoreFirewalldRuntimeDependents(ctx context.Context,
|
||||
return restoreErr
|
||||
}
|
||||
|
||||
func (s *FirewallService) runFirewallLifecycleTask(t *task.Task, client lifecycle.Client, request dto.FirewallLifecycleOperation, syncWhitelist bool) error {
|
||||
func (s *FirewallService) runFirewallLifecycleTask(t *task.Task, client lifecycle.Client, request dto.FirewallLifecycleOperation, prepareStart func(lifecycle.Client) error) error {
|
||||
ctx := t.TaskCtx
|
||||
provider := filter.Provider(client.Name())
|
||||
var prepareStart func(lifecycle.Client) error
|
||||
if syncWhitelist {
|
||||
prepareStart = func(lifecycle.Client) error {
|
||||
return runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallSyncWhitelistStep"), func() error {
|
||||
return s.SyncPortWhitelist(ctx)
|
||||
})
|
||||
}
|
||||
}
|
||||
operationErr := operateFirewallLifecycle(firewallLifecycleClient{client}, request.Operation, request.WithDockerRestart, prepareStart, t)
|
||||
if request.Operation == string(lifecycle.OperationStop) {
|
||||
return operationErr
|
||||
|
||||
@@ -554,6 +554,58 @@ func (s *FirewallService) checkSelectedProvider(ctx context.Context, requested f
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareFirewallRuleCreate(request dto.FirewallRuleCreate) (dto.FirewallRuleCreate, error) {
|
||||
if request.BackupFile != "" {
|
||||
if len(request.Items) != 0 {
|
||||
return request, filter.ErrInvalidRule
|
||||
}
|
||||
items, err := readFirewallBackup(request.BackupFile)
|
||||
if err != nil {
|
||||
return request, err
|
||||
}
|
||||
for _, item := range items {
|
||||
request.Items = append(request.Items, dto.FirewallRuleCreateItem{Rule: item.FirewallRule, Raw: item.Raw, ParseStatus: item.ParseStatus, SourceKind: constant.FirewallRuleSourceImported})
|
||||
}
|
||||
}
|
||||
|
||||
for index, item := range request.Items {
|
||||
if item.SourceKind != constant.FirewallRuleSourceImported || item.Rule.Scope.Provider != filter.ProviderUFW || item.ParseStatus != filter.ParseStatusPartial || item.Raw == "" {
|
||||
continue
|
||||
}
|
||||
observed := filterufw.ParseRules(item.Rule.Scope, item.Raw)
|
||||
if len(observed) == 1 && observed[0].ParseStatus == filter.ParseStatusSupported {
|
||||
observed[0].Rule.Description = item.Rule.Description
|
||||
request.Items[index].Rule = observed[0].Rule
|
||||
request.Items[index].ParseStatus, request.Items[index].Raw = filter.ParseStatusSupported, ""
|
||||
}
|
||||
}
|
||||
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (s *FirewallService) runFirewallCreateRulesTask(t *task.Task, request dto.FirewallRuleCreate, provider filter.Provider) error {
|
||||
ruleState := make(map[string]filter.RuleSet)
|
||||
firewallRuleMutationMu.Lock()
|
||||
if err := s.checkSelectedProvider(t.TaskCtx, provider); err != nil {
|
||||
firewallRuleMutationMu.Unlock()
|
||||
return err
|
||||
}
|
||||
_, err := s.createRules(t.TaskCtx, request, t, ruleState)
|
||||
firewallRuleMutationMu.Unlock()
|
||||
if !request.Initialize || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
ruleState = nil
|
||||
}
|
||||
whitelistErr := s.syncPortWhitelist(t.TaskCtx, ruleState)
|
||||
if whitelistErr == nil && provider == filter.ProviderFirewalld {
|
||||
whitelistErr = lifecycleproviders.RemoveFirewalldSSHService()
|
||||
}
|
||||
t.LogWithStatus(i18n.GetMsgByKey("FirewallSyncWhitelistStep"), whitelistErr)
|
||||
return errors.Join(err, whitelistErr)
|
||||
}
|
||||
|
||||
func (s *FirewallService) createRules(ctx context.Context, request dto.FirewallRuleCreate, t *task.Task, ruleState map[string]filter.RuleSet) (result dto.FirewallRuleCreateResponse, taskErr error) {
|
||||
var firstFailure error
|
||||
defer func() {
|
||||
@@ -1734,9 +1786,6 @@ func operateFirewallLifecycle(client lifecycle.Client, operation string, withDoc
|
||||
var recoveryErrors []error
|
||||
if prepareStart != nil {
|
||||
err := prepareStart(client)
|
||||
if err == nil && client.Name() == constant.FirewallProviderFirewalld {
|
||||
err = lifecycleproviders.RemoveFirewalldSSHService()
|
||||
}
|
||||
if err != nil {
|
||||
recoveryErrors = append(recoveryErrors, fmt.Errorf("prepare firewall after %s: %w", operation, err))
|
||||
}
|
||||
@@ -1758,17 +1807,82 @@ func operateFirewallLifecycle(client lifecycle.Client, operation string, withDoc
|
||||
}
|
||||
|
||||
func (c firewallLifecycleClient) Start() error {
|
||||
firewallWhitelistMu.Lock()
|
||||
defer firewallWhitelistMu.Unlock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
if err := c.prepareAccessWhitelistBeforeStart(); err != nil {
|
||||
global.LOG.Warnf("prepare %s access whitelist before start failed: %v", c.Name(), err)
|
||||
}
|
||||
return c.Client.Start()
|
||||
}
|
||||
|
||||
func (c firewallLifecycleClient) Restart() error {
|
||||
firewallWhitelistMu.Lock()
|
||||
defer firewallWhitelistMu.Unlock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
if err := c.prepareAccessWhitelistBeforeStart(); err != nil {
|
||||
global.LOG.Warnf("prepare %s access whitelist before restart failed: %v", c.Name(), err)
|
||||
}
|
||||
return c.Client.Restart()
|
||||
}
|
||||
|
||||
func (c firewallLifecycleClient) prepareAccessWhitelistBeforeStart() error {
|
||||
provider := filter.Provider(c.Name())
|
||||
if provider != filter.ProviderFirewalld && provider != filter.ProviderUFW {
|
||||
return nil
|
||||
}
|
||||
ports, err := LoadRequiredFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var adapter filter.Adapter
|
||||
var active bool
|
||||
if provider == filter.ProviderFirewalld {
|
||||
active, err = c.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
adapter = filterfirewalld.NewAdapter()
|
||||
} else {
|
||||
adapter = filterufw.NewAdapter()
|
||||
}
|
||||
var commands []filter.NativeCommand
|
||||
for _, port := range firewall.ExpandPortWhitelist(ports) {
|
||||
rule := firewall.RuleForSystemPort(provider, port)
|
||||
rule.Scope = rule.Scope.Normalize()
|
||||
rule.UUID = uuid.NewString()
|
||||
if provider == filter.ProviderUFW {
|
||||
position := int64(1)
|
||||
rule.OrderIndex = &position
|
||||
}
|
||||
plan, err := adapter.BuildCommands(filter.RuleSet{Scope: rule.Scope}, []filter.RuleChange{{Operation: filter.ChangeCreate, After: &rule}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range plan.Rules {
|
||||
for _, command := range item.Commands {
|
||||
if provider == filter.ProviderFirewalld {
|
||||
if active {
|
||||
command.Args = append(command.Args, "--permanent")
|
||||
} else {
|
||||
command.Executable = "firewall-offline-cmd"
|
||||
}
|
||||
}
|
||||
commands = append(commands, command)
|
||||
}
|
||||
}
|
||||
}
|
||||
manager := cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second), cmd.WithEnv("LC_ALL=C", "LANGUAGE=en_US:en"))
|
||||
for _, command := range commands {
|
||||
if err := manager.RunWithOptionalSudo(command.Executable, command.Args...); err != nil {
|
||||
return fmt.Errorf("prepare %s access whitelist before start: %w", provider, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runFirewallLifecycleAction(t *task.Task, name string, action func() error) error {
|
||||
t.Log(i18n.GetWithName("TaskStart", name))
|
||||
started := time.Now()
|
||||
|
||||
@@ -55,7 +55,7 @@ type IMonitorService interface {
|
||||
LoadIOOptions() []string
|
||||
LoadNetworkOptions() []string
|
||||
LoadVLLMMonitorData(ctx context.Context, req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error)
|
||||
LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error)
|
||||
LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (dto.MonitorVLLMCurrentData, error)
|
||||
CleanVLLMMonitor(req dto.MonitorVLLMClean) error
|
||||
LoadGPUOptions() dto.MonitorGPUOptions
|
||||
LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error)
|
||||
|
||||
@@ -60,6 +60,9 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
for i := range points {
|
||||
points[i].MonitorVLLM.CompletedRequests = points[i].CompletedRequests
|
||||
}
|
||||
if data.BucketSeconds > 0 {
|
||||
aggregates := map[int64]*vllm.HistoryAccumulator{}
|
||||
err = vllmMonitorRepo.WalkDeltas(ctx, req.AppInstallID, req.StartTime, req.EndTime, data.BucketSeconds, req.Aggregation, req.Group, func(bucket int64, histograms, requests string) error {
|
||||
@@ -91,15 +94,6 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
|
||||
data.Points = append(data.Points, point)
|
||||
}
|
||||
} else {
|
||||
setting, err := settingRepo.GetValueByKey("VLLMMonitorInterval")
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
intervalSeconds, err := strconv.Atoi(setting)
|
||||
if err != nil || intervalSeconds <= 0 {
|
||||
return data, fmt.Errorf("invalid vLLM monitoring interval: %s", setting)
|
||||
}
|
||||
interval := time.Duration(intervalSeconds) * time.Second
|
||||
for i, point := range points {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return data, err
|
||||
@@ -109,8 +103,20 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
|
||||
return data, err
|
||||
}
|
||||
aggregate.Apply(&point.MonitorVLLM, "max")
|
||||
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})
|
||||
if i > 0 {
|
||||
previous := points[i-1]
|
||||
interval := time.Duration(max(previous.SampleIntervalSeconds, point.SampleIntervalSeconds)) * time.Second
|
||||
if interval == 0 {
|
||||
if i > 1 {
|
||||
interval = previous.CreatedAt.Sub(points[i-2].CreatedAt)
|
||||
}
|
||||
if i+1 < len(points) {
|
||||
interval = max(interval, points[i+1].CreatedAt.Sub(point.CreatedAt))
|
||||
}
|
||||
}
|
||||
if interval > 0 && point.CreatedAt.Sub(previous.CreatedAt) > 2*interval {
|
||||
data.Points = append(data.Points, model.MonitorVLLM{CreatedAt: previous.CreatedAt.Add(interval), AppInstallID: req.AppInstallID})
|
||||
}
|
||||
}
|
||||
data.Points = append(data.Points, point.MonitorVLLM)
|
||||
}
|
||||
@@ -118,31 +124,30 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error) {
|
||||
func (m *MonitorService) LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (dto.MonitorVLLMCurrentData, error) {
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID))
|
||||
if err != nil {
|
||||
return model.MonitorVLLM{}, err
|
||||
return dto.MonitorVLLMCurrentData{}, err
|
||||
}
|
||||
if install.App.Key != "vllm" {
|
||||
return model.MonitorVLLM{}, fmt.Errorf("not a vLLM installation")
|
||||
return dto.MonitorVLLMCurrentData{}, fmt.Errorf("not a vLLM installation")
|
||||
}
|
||||
previous, err := collectVLLMMetrics(ctx, install, model.MonitorVLLM{})
|
||||
point, err := collectVLLMMetrics(ctx, install, model.MonitorVLLM{})
|
||||
data := dto.MonitorVLLMCurrentData{MonitorVLLM: point}
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Collect vLLM metrics on port %d failed: %v", install.HttpPort, err)
|
||||
return previous, nil
|
||||
return data, nil
|
||||
}
|
||||
timer := time.NewTimer(time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return model.MonitorVLLM{}, ctx.Err()
|
||||
case <-timer.C:
|
||||
if err := json.Unmarshal([]byte(point.RawMetrics), &data.Metrics); err != nil {
|
||||
return data, err
|
||||
}
|
||||
point, err := collectVLLMMetrics(ctx, install, previous)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Collect vLLM metrics on port %d failed: %v", install.HttpPort, err)
|
||||
for name := range data.Metrics {
|
||||
if name == "request_prompt_tokens_sum" || name == "request_prompt_tokens_count" ||
|
||||
(!strings.HasSuffix(name, "_total") && !strings.HasSuffix(name, "_sum") && !strings.HasSuffix(name, "_count")) {
|
||||
delete(data.Metrics, name)
|
||||
}
|
||||
}
|
||||
return point, nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) CleanVLLMMonitor(req dto.MonitorVLLMClean) error {
|
||||
@@ -164,7 +169,7 @@ func StartVLLMMonitor(interval string) error {
|
||||
return fmt.Errorf("invalid vLLM monitoring interval: %s", interval)
|
||||
}
|
||||
service := &MonitorService{}
|
||||
job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(service.saveVLLMData))
|
||||
job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(func() { service.saveVLLMData(seconds) }))
|
||||
id, err := global.Cron.AddFunc(fmt.Sprintf("@every %ds", seconds), func() { go job.Run() })
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -176,7 +181,7 @@ func StartVLLMMonitor(interval string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) saveVLLMData() {
|
||||
func (m *MonitorService) saveVLLMData(intervalSeconds int) {
|
||||
if !vllmMonitorMutex.TryLock() {
|
||||
return
|
||||
}
|
||||
@@ -228,6 +233,7 @@ func (m *MonitorService) saveVLLMData() {
|
||||
return err
|
||||
}
|
||||
point, err := collectVLLMMetrics(context.Background(), install, previous)
|
||||
point.SampleIntervalSeconds = intervalSeconds
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Collect vLLM metrics for %s failed: %v", install.Name, err)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ const (
|
||||
AppOpenclaw = "openclaw"
|
||||
AppCopaw = "copaw"
|
||||
AppHermesAgent = "hermes-agent"
|
||||
AppDeepSeekHarness = "deepseek-harness"
|
||||
AppOctop = "octop"
|
||||
AppMysql = "mysql"
|
||||
AppMariaDB = "mariadb"
|
||||
AppPostgresql = "postgresql"
|
||||
|
||||
@@ -114,6 +114,8 @@ func agentDBMigrations() []*gormigrate.Migration {
|
||||
migrations.AddVLLMMonitor,
|
||||
migrations.AddMonitorSettings,
|
||||
migrations.AddVLLMRequestMetrics,
|
||||
migrations.AddVLLMSampleInterval,
|
||||
migrations.AddNativeAgentInstalls,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var AddNativeAgentInstalls = &gormigrate.Migration{
|
||||
ID: "20261020-add-deepseek-harness-octop-agent-installs",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var installs []struct {
|
||||
model.AppInstall
|
||||
AgentType string
|
||||
}
|
||||
if err := tx.Table("app_installs").
|
||||
Select("app_installs.*, apps.key AS agent_type").
|
||||
Joins("JOIN apps ON apps.id = app_installs.app_id").
|
||||
Where("apps.key IN ?", []string{constant.AppDeepSeekHarness, constant.AppOctop}).
|
||||
Where("NOT EXISTS (SELECT 1 FROM agents WHERE agents.app_install_id = app_installs.id)").
|
||||
Order("app_installs.id").
|
||||
Scan(&installs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, install := range installs {
|
||||
var count int64
|
||||
if err := tx.Model(&model.Agent{}).Where("LOWER(name) = LOWER(?)", install.Name).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
global.LOG.Warnf("skip agent registration for app install %d: name already exists", install.ID)
|
||||
continue
|
||||
}
|
||||
agent := model.Agent{
|
||||
BaseModel: model.BaseModel{CreatedAt: install.CreatedAt, UpdatedAt: install.UpdatedAt},
|
||||
Name: install.Name, AgentType: install.AgentType, AppInstallID: install.ID,
|
||||
Status: install.Status, Message: install.Message,
|
||||
}
|
||||
if err := tx.Create(&agent).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -1881,3 +1881,10 @@ var AddVLLMRequestMetrics = &gormigrate.Migration{
|
||||
return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{})
|
||||
},
|
||||
}
|
||||
|
||||
var AddVLLMSampleInterval = &gormigrate.Migration{
|
||||
ID: "20261016-vllm-sample-interval",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -314,7 +314,17 @@ func (s *Session) detach(a *attachment, clean, revalidate bool, cursor uint64) {
|
||||
if !s.Persistent || revalidate {
|
||||
timeout = revalidateGrace
|
||||
}
|
||||
s.grace = time.AfterFunc(timeout, s.Close)
|
||||
// Stop cannot cancel a fired timer; a racing Attach clears s.grace.
|
||||
var grace *time.Timer
|
||||
grace = time.AfterFunc(timeout, func() {
|
||||
s.mu.Lock()
|
||||
expired := s.grace == grace
|
||||
s.mu.Unlock()
|
||||
if expired {
|
||||
s.Close()
|
||||
}
|
||||
})
|
||||
s.grace = grace
|
||||
}
|
||||
s.mu.Unlock()
|
||||
if clean {
|
||||
|
||||
@@ -266,7 +266,6 @@ func (b *BaseApi) GetLoginSetting(c *gin.Context) {
|
||||
Language: settingInfo.Language,
|
||||
MenuTabs: settingInfo.MenuTabs,
|
||||
MenuAccordion: settingInfo.MenuAccordion,
|
||||
PanelCache: settingInfo.PanelCache,
|
||||
PanelName: settingInfo.PanelName,
|
||||
Theme: settingInfo.Theme,
|
||||
NeedCaptcha: needCaptcha,
|
||||
|
||||
@@ -4,16 +4,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const DefaultPanelCache = `{"tabs":true,"pagination":true,"refresh":true,"search":false,"duration":"24h"}`
|
||||
|
||||
type PanelCache struct {
|
||||
Tabs *bool `json:"tabs"`
|
||||
Pagination *bool `json:"pagination"`
|
||||
Refresh *bool `json:"refresh"`
|
||||
Search *bool `json:"search"`
|
||||
Duration string `json:"duration"`
|
||||
}
|
||||
|
||||
type SettingInfo struct {
|
||||
SystemVersion string `json:"systemVersion"`
|
||||
DeveloperMode string `json:"developerMode"`
|
||||
@@ -28,7 +18,6 @@ type SettingInfo struct {
|
||||
Theme string `json:"theme"`
|
||||
MenuTabs string `json:"menuTabs"`
|
||||
MenuAccordion string `json:"menuAccordion"`
|
||||
PanelCache string `json:"panelCache"`
|
||||
Language string `json:"language"`
|
||||
DocSource string `json:"docSource"`
|
||||
|
||||
@@ -73,7 +62,6 @@ type SettingBaseInfo struct {
|
||||
Theme string `json:"theme"`
|
||||
MenuTabs string `json:"menuTabs"`
|
||||
MenuAccordion string `json:"menuAccordion"`
|
||||
PanelCache string `json:"panelCache"`
|
||||
Language string `json:"language"`
|
||||
HideMenu string `json:"hideMenu"`
|
||||
DocSource string `json:"docSource"`
|
||||
@@ -300,7 +288,6 @@ type LoginSetting struct {
|
||||
Language string `json:"language"`
|
||||
MenuTabs string `json:"menuTabs"`
|
||||
MenuAccordion string `json:"menuAccordion"`
|
||||
PanelCache string `json:"panelCache"`
|
||||
PanelName string `json:"panelName"`
|
||||
Theme string `json:"theme"`
|
||||
NeedCaptcha bool `json:"needCaptcha"`
|
||||
|
||||
@@ -120,9 +120,6 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
|
||||
for _, set := range setting {
|
||||
settingMap[set.Key] = set.Value
|
||||
}
|
||||
if settingMap["PanelCache"] == "" {
|
||||
settingMap["PanelCache"] = dto.DefaultPanelCache
|
||||
}
|
||||
repairAndSortHideMenu(settingMap)
|
||||
var info dto.SettingInfo
|
||||
arr, err := json.Marshal(settingMap)
|
||||
@@ -158,9 +155,6 @@ func (u *SettingService) GetSettingBaseInfo() (*dto.SettingBaseInfo, error) {
|
||||
for _, set := range setting {
|
||||
settingMap[set.Key] = set.Value
|
||||
}
|
||||
if settingMap["PanelCache"] == "" {
|
||||
settingMap["PanelCache"] = dto.DefaultPanelCache
|
||||
}
|
||||
repairAndSortHideMenu(settingMap)
|
||||
var info dto.SettingBaseInfo
|
||||
arr, err := json.Marshal(settingMap)
|
||||
@@ -230,27 +224,6 @@ func (u *SettingService) Update(c *gin.Context, key, value string) error {
|
||||
if auth.IsAPICredentialSetting(key) {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
if key == "PanelCache" {
|
||||
var config dto.PanelCache
|
||||
decoder := json.NewDecoder(strings.NewReader(value))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&config); err != nil {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
if err := decoder.Decode(new(any)); err != io.EOF {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
if config.Tabs == nil || config.Pagination == nil || config.Refresh == nil || config.Search == nil {
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
switch config.Duration {
|
||||
case "24h", "week", "month", "forever":
|
||||
default:
|
||||
return buserr.New("ErrInvalidParams")
|
||||
}
|
||||
data, _ := json.Marshal(config)
|
||||
return settingRepo.UpdateOrCreate(key, string(data))
|
||||
}
|
||||
oldVal, err := settingRepo.Get(repo.WithByKey(key))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "An unrevoked API key with this name already exists"
|
||||
ErrAPIKeyNotFound: "The API key does not exist or does not belong to the current user"
|
||||
ErrAPIKeyAppBindingDisabled: "APP QR binding is not allowed for this API key"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "Unable to verify API key terminal authorization support. Check the node connection and upgrade its Agent before retrying."
|
||||
LocalAppSyncDispatch: "Dispatch local app sync"
|
||||
LocalAppSyncDispatchNode: "Dispatch local app sync to {{.node}} (node task: {{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "Local app sync across nodes is disabled"
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "Ya existe una clave API no revocada con este nombre"
|
||||
ErrAPIKeyNotFound: "La clave API no existe o no pertenece al usuario actual"
|
||||
ErrAPIKeyAppBindingDisabled: "Esta clave API no permite vincular la APP mediante código QR"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "No se pudo verificar la autorización de terminal con clave API. Compruebe la conexión del nodo y actualice su Agent antes de reintentar."
|
||||
LocalAppSyncDispatch: "Enviar sincronización de apps locales"
|
||||
LocalAppSyncDispatchNode: "Enviar sincronización de apps locales a {{.node}} (tarea del nodo: {{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "La sincronización de apps locales entre nodos está desactivada"
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "یک کلید API لغونشده با این نام از
|
||||
ErrAPIKeyNotFound: "کلید API وجود ندارد یا متعلق به کاربر فعلی نیست"
|
||||
ErrAPIKeyAppBindingDisabled: "اتصال برنامه با کد QR برای این کلید API مجاز نیست"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "امکان بررسی پشتیبانی از مجوزدهی ترمینال با کلید API وجود ندارد. اتصال گره را بررسی و Agent آن را ارتقا دهید، سپس دوباره تلاش کنید."
|
||||
LocalAppSyncDispatch: "ارسال درخواست همگامسازی برنامههای محلی"
|
||||
LocalAppSyncDispatchNode: "ارسال درخواست همگامسازی به {{.node}} (وظیفه گره: {{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "همگامسازی برنامههای محلی بین گرهها غیرفعال است"
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "同じ名前の未失効 API キーが既に存在しま
|
||||
ErrAPIKeyNotFound: "API キーが存在しないか、現在のユーザーに属していません"
|
||||
ErrAPIKeyAppBindingDisabled: "この API キーでは APP の QR コード連携が許可されていません"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "API キーによるターミナル認証への対応を確認できません。ノードの接続を確認し、Agent を更新して再試行してください。"
|
||||
LocalAppSyncDispatch: "ローカルアプリ同期要求の送信"
|
||||
LocalAppSyncDispatchNode: "{{.node}} にローカルアプリ同期要求を送信(ノードタスク: {{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "ノード間のローカルアプリ同期は無効です"
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "같은 이름의 폐기되지 않은 API Key가 이미 있
|
||||
ErrAPIKeyNotFound: "API Key가 없거나 현재 사용자 소유가 아닙니다"
|
||||
ErrAPIKeyAppBindingDisabled: "이 API Key는 APP QR 코드 연결을 허용하지 않습니다"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "API Key 터미널 인증 지원 여부를 확인할 수 없습니다. 노드 연결을 확인하고 Agent를 업그레이드한 후 다시 시도하세요."
|
||||
LocalAppSyncDispatch: "로컬 앱 동기화 요청 전송"
|
||||
LocalAppSyncDispatchNode: "{{.node}}에 로컬 앱 동기화 요청 전송 (노드 작업: {{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "노드 간 로컬 앱 동기화가 비활성화되어 있습니다"
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "ມີ API Key ທີ່ຍັງບໍ່ຖືກຖອ
|
||||
ErrAPIKeyNotFound: "ບໍ່ພົບ API Key ຫຼືບໍ່ແມ່ນຂອງຜູ້ໃຊ້ປັດຈຸບັນ"
|
||||
ErrAPIKeyAppBindingDisabled: "API Key ນີ້ບໍ່ອະນຸຍາດໃຫ້ຜູກ APP ຜ່ານລະຫັດ QR"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "ບໍ່ສາມາດຢືນຢັນການຮອງຮັບການອະນຸຍາດເທີມິນອນດ້ວຍ API Key. ກວດສອບການເຊື່ອມຕໍ່ໂນດ ແລະອັບເກຣດ Agent ກ່ອນລອງອີກ."
|
||||
LocalAppSyncDispatch: "ສົ່ງຄຳຂໍຊິງຄ໌ແອັບໃນເຄື່ອງ"
|
||||
LocalAppSyncDispatchNode: "ສົ່ງຄຳຂໍຊິງຄ໌ໄປຫາ {{.node}} (ວຽກຂອງໂນດ: {{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "ການຊິງຄ໌ແອັບລະຫວ່າງໂນດຍັງບໍ່ໄດ້ເປີດ"
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "Kunci API yang belum dibatalkan dengan nama ini sudah wuju
|
||||
ErrAPIKeyNotFound: "Kunci API tidak wujud atau bukan milik pengguna semasa"
|
||||
ErrAPIKeyAppBindingDisabled: "Pemautan APP melalui kod QR tidak dibenarkan untuk kunci API ini"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "Sokongan pengesahan terminal melalui kunci API tidak dapat disahkan. Semak sambungan nod dan naik taraf Agent sebelum mencuba lagi."
|
||||
LocalAppSyncDispatch: "Hantar penyegerakan aplikasi tempatan"
|
||||
LocalAppSyncDispatchNode: "Hantar penyegerakan aplikasi tempatan ke {{.node}} (tugas nod: {{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "Penyegerakan aplikasi tempatan antara nod dinyahdayakan"
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "Já existe uma chave API não revogada com esse nome"
|
||||
ErrAPIKeyNotFound: "A chave API não existe ou não pertence ao usuário atual"
|
||||
ErrAPIKeyAppBindingDisabled: "Esta chave API não permite vincular o APP por código QR"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "Não foi possível verificar o suporte à autorização de terminal por chave API. Verifique a conexão do nó e atualize o Agent antes de tentar novamente."
|
||||
LocalAppSyncDispatch: "Enviar sincronização de apps locais"
|
||||
LocalAppSyncDispatchNode: "Enviar sincronização de apps locais para {{.node}} (tarefa do nó: {{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "A sincronização de apps locais entre nós está desativada"
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "Неотозванный ключ API с таким име
|
||||
ErrAPIKeyNotFound: "Ключ API не существует или не принадлежит текущему пользователю"
|
||||
ErrAPIKeyAppBindingDisabled: "Для этого ключа API привязка приложения через QR-код запрещена"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "Не удалось проверить поддержку авторизации терминала по ключу API. Проверьте подключение узла, обновите его Agent и повторите попытку."
|
||||
LocalAppSyncDispatch: "Отправка запросов синхронизации локальных приложений"
|
||||
LocalAppSyncDispatchNode: "Отправка запроса синхронизации узлу {{.node}} (задача узла: {{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "Синхронизация локальных приложений между узлами отключена"
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "Bu adda iptal edilmemiş bir API anahtarı zaten var"
|
||||
ErrAPIKeyNotFound: "API anahtarı mevcut değil veya geçerli kullanıcıya ait değil"
|
||||
ErrAPIKeyAppBindingDisabled: "Bu API anahtarı için QR koduyla APP bağlantısına izin verilmiyor"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "API anahtarıyla terminal yetkilendirme desteği doğrulanamadı. Düğüm bağlantısını kontrol edip Agent sürümünü yükselttikten sonra tekrar deneyin."
|
||||
LocalAppSyncDispatch: "Yerel uygulama eşitleme isteği gönder"
|
||||
LocalAppSyncDispatchNode: "{{.node}} düğümüne yerel uygulama eşitleme isteği gönder (düğüm görevi: {{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "Düğümler arası yerel uygulama eşitleme devre dışı"
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "已存在同名且未撤銷的 API Key"
|
||||
ErrAPIKeyNotFound: "API Key 不存在或不屬於目前使用者"
|
||||
ErrAPIKeyAppBindingDisabled: "此 API Key 未允許 APP 掃碼綁定"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "無法確認節點支援 API Key 終端授權,請檢查節點連線並升級 Agent 後重試。"
|
||||
LocalAppSyncDispatch: "下發本機應用同步"
|
||||
LocalAppSyncDispatchNode: "向節點 {{.node}} 下發本機應用同步(節點任務:{{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "尚未啟用本機應用跨節點同步"
|
||||
|
||||
@@ -499,3 +499,6 @@ ErrAPIKeyNameExists: "已存在同名的未撤销 API Key"
|
||||
ErrAPIKeyNotFound: "API Key 不存在或不属于当前用户"
|
||||
ErrAPIKeyAppBindingDisabled: "此 API Key 未允许 APP 扫码绑定"
|
||||
ErrAPIKeyTerminalUpgradeRequired: "无法确认节点支持 API Key 终端授权,请检查节点连接并升级 Agent 后重试。"
|
||||
LocalAppSyncDispatch: "下发本地应用同步"
|
||||
LocalAppSyncDispatchNode: "向节点 {{.node}} 下发本地应用同步(节点任务:{{.taskID}})"
|
||||
ErrLocalAppSyncDisabled: "尚未开启本地应用跨节点同步"
|
||||
|
||||
@@ -26,7 +26,6 @@ func Init() {
|
||||
}
|
||||
|
||||
var baseSettingKeys = map[string]struct{}{
|
||||
"PanelCache": {},
|
||||
"PanelName": {},
|
||||
"Theme": {},
|
||||
"MenuTabs": {},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ReqPage } from '.';
|
||||
|
||||
export namespace AI {
|
||||
export type AgentType = 'openclaw' | 'copaw' | 'hermes-agent';
|
||||
export type AgentType = 'openclaw' | 'copaw' | 'hermes-agent' | 'deepseek-harness' | 'octop';
|
||||
|
||||
export interface OllamaModelInfo {
|
||||
id: number;
|
||||
@@ -423,6 +423,7 @@ export namespace AI {
|
||||
}
|
||||
|
||||
export interface AgentCreateReq {
|
||||
accessHost?: string;
|
||||
name: string;
|
||||
remark: string;
|
||||
appVersion: string;
|
||||
@@ -450,6 +451,7 @@ export namespace AI {
|
||||
}
|
||||
|
||||
export interface AgentItem {
|
||||
accessHost: string;
|
||||
id: number;
|
||||
name: string;
|
||||
remark: string;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { ReqPage, CommonModel } from '.';
|
||||
|
||||
export namespace App {
|
||||
export interface LocalAppSyncConfig {
|
||||
enabled: boolean;
|
||||
}
|
||||
export interface App extends CommonModel {
|
||||
name: string;
|
||||
icon?: string;
|
||||
|
||||
@@ -77,7 +77,6 @@ export namespace Login {
|
||||
language: string;
|
||||
menuTabs: string;
|
||||
menuAccordion: string;
|
||||
panelCache: string;
|
||||
panelName: string;
|
||||
theme: string;
|
||||
isOffline: boolean;
|
||||
|
||||
@@ -41,7 +41,6 @@ export namespace Setting {
|
||||
theme: string;
|
||||
menuTabs: string;
|
||||
menuAccordion: string;
|
||||
panelCache: string;
|
||||
language: string;
|
||||
docSource: string;
|
||||
|
||||
@@ -87,7 +86,6 @@ export namespace Setting {
|
||||
theme: string;
|
||||
menuTabs: string;
|
||||
menuAccordion: string;
|
||||
panelCache: string;
|
||||
language: string;
|
||||
hideMenu: string;
|
||||
docSource: string;
|
||||
|
||||
@@ -144,3 +144,15 @@ export function getAppIconUrl(appKey: string, node?: string): string {
|
||||
export const installAppToNodes = (param: App.InstallAppToNodes) => {
|
||||
return http.post(`/core/xpack/sync/app/install`, param);
|
||||
};
|
||||
|
||||
export const getLocalAppSyncConfig = () => {
|
||||
return http.get<App.LocalAppSyncConfig>('/core/xpack/settings/app/local-sync');
|
||||
};
|
||||
|
||||
export const updateLocalAppSyncConfig = (config: App.LocalAppSyncConfig) => {
|
||||
return http.post('/core/xpack/settings/app/local-sync', config);
|
||||
};
|
||||
|
||||
export const dispatchLocalAppSync = (req: App.AppStoreSync) => {
|
||||
return http.post('/core/xpack/settings/app/local-sync/dispatch', req);
|
||||
};
|
||||
|
||||
@@ -21,10 +21,10 @@ export const loadForwardBaseInfo = () =>
|
||||
export const searchForwardRule = (request: Firewall.ForwardRuleSearch) =>
|
||||
http.post<ResPage<Firewall.RuleInfo>>('/hosts/firewall/forward/search', request, TimeoutEnum.T_40S);
|
||||
|
||||
export const operateFire = (operation: string, withDockerRestart: boolean) =>
|
||||
export const operateFire = (operation: string, withDockerRestart: boolean, backupFile?: string) =>
|
||||
http.post<Firewall.FilterChainOperationResult>(
|
||||
'/hosts/firewall/operate',
|
||||
{ operation, withDockerRestart },
|
||||
{ operation, withDockerRestart, backupFile },
|
||||
TimeoutEnum.T_10M,
|
||||
);
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
@@ -20,55 +20,57 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onActivated, onDeactivated, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { panelCacheKey, readPanelCache, writePanelCache } from '@/utils/panel-cache';
|
||||
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
defineOptions({ name: 'TableSetting' });
|
||||
const refreshRateUnit = ref(0);
|
||||
|
||||
const refreshRateUnit = ref<number>(0);
|
||||
const emit = defineEmits(['search']);
|
||||
const props = defineProps({ title: String, rate: Number });
|
||||
const route = useRoute();
|
||||
// Capture the owning page; useRoute continues to change while a page is deactivated.
|
||||
const pagePath = route.path;
|
||||
const cacheKey = () => panelCacheKey(`refresh:${pagePath}:${props.title || ''}`);
|
||||
let timer: ReturnType<typeof setInterval> | undefined;
|
||||
let active = false;
|
||||
const stopTimer = () => {
|
||||
clearInterval(timer);
|
||||
timer = undefined;
|
||||
};
|
||||
const startTimer = () => {
|
||||
stopTimer();
|
||||
if (active && refreshRateUnit.value > 0) {
|
||||
timer = setInterval(() => emit('search'), 1000 * refreshRateUnit.value);
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
rate: Number,
|
||||
});
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
const changeRefresh = (command: number) => {
|
||||
refreshRateUnit.value = command || 0;
|
||||
if (refreshRateUnit.value !== 0) {
|
||||
if (timer) {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
}
|
||||
timer = setInterval(() => {
|
||||
emit('search');
|
||||
}, 1000 * refreshRateUnit.value);
|
||||
} else {
|
||||
if (timer) {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
localStorage.setItem(props.title, refreshRateUnit.value + '');
|
||||
};
|
||||
const restore = () => {
|
||||
const rate = readPanelCache<number>('refresh', cacheKey());
|
||||
refreshRateUnit.value = Number.isFinite(rate) && rate >= 0 ? rate : props.rate || 0;
|
||||
startTimer();
|
||||
};
|
||||
const changeRefresh = (rate: number) => {
|
||||
refreshRateUnit.value = rate;
|
||||
writePanelCache('refresh', cacheKey(), rate);
|
||||
startTimer();
|
||||
};
|
||||
const activate = () => {
|
||||
if (active) return;
|
||||
active = true;
|
||||
restore();
|
||||
};
|
||||
watch(cacheKey, () => {
|
||||
if (active) restore();
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
if (props.title) {
|
||||
localStorage.setItem(props.title, refreshRateUnit.value + '');
|
||||
}
|
||||
});
|
||||
onMounted(activate);
|
||||
onActivated(activate);
|
||||
onDeactivated(() => {
|
||||
active = false;
|
||||
stopTimer();
|
||||
|
||||
onMounted(() => {
|
||||
if (props.title && localStorage.getItem(props.title) != null) {
|
||||
let rate = Number(localStorage.getItem(props.title));
|
||||
refreshRateUnit.value = rate ? Number(rate) : 0;
|
||||
changeRefresh(refreshRateUnit.value);
|
||||
return;
|
||||
}
|
||||
if (props.rate) {
|
||||
refreshRateUnit.value = props.rate;
|
||||
changeRefresh(refreshRateUnit.value);
|
||||
}
|
||||
});
|
||||
onUnmounted(stopTimer);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -167,7 +167,6 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { panelCacheKey, writePanelCache } from '@/utils/panel-cache';
|
||||
import { computed, onDeactivated, onMounted, ref, useAttrs } from 'vue';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { flattenVNodes, type FuTableOperationButton } from '@/components/table/shared';
|
||||
@@ -342,33 +341,13 @@ const handleRightClick = (row, column, event) => {
|
||||
openContextMenu(row, event);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
const pagination = props.paginationConfig;
|
||||
if (!pagination || !pagination.pageSize) return;
|
||||
const lastPage = Math.max(1, Math.ceil(pagination.total / pagination.pageSize));
|
||||
if (pagination.currentPage > lastPage) {
|
||||
pagination.currentPage = lastPage;
|
||||
emit('search');
|
||||
}
|
||||
},
|
||||
{ flush: 'post' },
|
||||
);
|
||||
|
||||
function currentChange() {
|
||||
emit('search');
|
||||
}
|
||||
|
||||
function sizeChange() {
|
||||
if (props.paginationConfig.cacheSizeKey) {
|
||||
writePanelCache(
|
||||
'pagination',
|
||||
panelCacheKey(`size:${props.paginationConfig.cacheSizeKey}`),
|
||||
props.paginationConfig.pageSize,
|
||||
);
|
||||
}
|
||||
props.paginationConfig.currentPage = 1;
|
||||
localStorage.setItem(props.paginationConfig.cacheSizeKey, props.paginationConfig.pageSize);
|
||||
emit('search');
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { readCachedPageSize } from '@/utils/panel-cache';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import NodeSelect from '@/components/node-select/index.vue';
|
||||
|
||||
@@ -74,7 +73,7 @@ const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'task-list-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: readCachedPageSize('task-list-page-size') || 20,
|
||||
pageSize: Number(localStorage.getItem('task-list-page-size')) || 20,
|
||||
total: 0,
|
||||
small: true,
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
size="w-70"
|
||||
:show-close="false"
|
||||
:modal="false"
|
||||
modal-penetrable
|
||||
@opened="claim"
|
||||
>
|
||||
<template #header>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:ref="(el: any) => store.setInstance(item.key, el)"
|
||||
@session="(id: string) => store.setSessionId(item.key, id)"
|
||||
@expired="store.onExpired(item.key)"
|
||||
@reconnect="store.reconnect(item.key)"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
@@ -31,7 +31,7 @@ const { currentNode } = useGlobalStore();
|
||||
|
||||
// session: agent side session id known (fresh or reattached)
|
||||
// expired: the agent no longer has the session; a reconnect must open a new one
|
||||
const emit = defineEmits(['session', 'expired']);
|
||||
const emit = defineEmits(['session', 'expired', 'reconnect']);
|
||||
|
||||
// Close codes of the agent's session protocol (agent/utils/terminal/session.go).
|
||||
const CLOSE_SESSION_NOT_FOUND = 4404;
|
||||
@@ -56,6 +56,7 @@ let closing = false;
|
||||
let reconnecting = false;
|
||||
let reconnectNoticeShown = false;
|
||||
let revalidating = false;
|
||||
let expired = false;
|
||||
let reconnectStartedAt = 0;
|
||||
let reconnectDelay = 1000;
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -279,6 +280,7 @@ function resizeTerminal() {
|
||||
if (terminalElement.value.clientWidth <= 0 || terminalElement.value.clientHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
if (terminalElement.value.closest('.terminal-host')) return;
|
||||
|
||||
fitAddon.fit();
|
||||
if (isWsOpen()) {
|
||||
@@ -484,6 +486,7 @@ const closeRealTerminal = (ev: CloseEvent) => {
|
||||
'31',
|
||||
ev.code === 1000 ? 'The connection has been disconnected.' : i18n.global.t('terminal.sessionExpired'),
|
||||
);
|
||||
expired = true;
|
||||
emit('expired');
|
||||
return;
|
||||
case CLOSE_ATTACHED_ELSEWHERE:
|
||||
@@ -602,6 +605,13 @@ function sendMsg(data: string, line: string = '') {
|
||||
|
||||
function onTermData(data: string) {
|
||||
if (!data) return;
|
||||
if (expired) {
|
||||
if (isEnterInputData(data)) {
|
||||
expired = false;
|
||||
emit('reconnect');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (aiNotice.value.loading) return;
|
||||
sendMsg(data, isEnterInputData(data) ? getCurrentTerminalLine() : '');
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
};
|
||||
@@ -773,6 +773,15 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
"Enter the IPv4 address or hostname used in your browser, without a scheme, port or path. HTTPS uses the application's local certificate.",
|
||||
deepSeekAuthHelper:
|
||||
'Username: letters, numbers, dot, underscore or hyphen. Password: at least 12 characters, without leading/trailing spaces or line breaks.',
|
||||
octopAuthHelper:
|
||||
'Password: at least 8 characters with letters and digits; avoid common passwords. If rejected during initialization, check the application logs for setup instructions.',
|
||||
initialCredentials: "Initial credentials; use the application's current password if changed.",
|
||||
nativeAgentAccessUnavailable:
|
||||
'The existing installation has no compatible HTTPS access address or port. Check its application settings.',
|
||||
agent: 'Agent',
|
||||
account: 'Model Account',
|
||||
batchOperation: 'Batch Operation',
|
||||
@@ -1517,6 +1526,14 @@ const message = {
|
||||
extraHeaders: 'Extra Headers',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Current concurrency',
|
||||
sampleMoment: 'At sampling time',
|
||||
sampleWindow: 'Sample interval',
|
||||
requestUnit: 'requests',
|
||||
processing: 'Processing',
|
||||
queued: 'Queued',
|
||||
requestTiming: 'Request timing',
|
||||
completedRequests: 'Requests completed in interval',
|
||||
tokensPerSecond: 'tokens/s',
|
||||
requestsPerSecond: 'Requests/s',
|
||||
finishReasonLabels: {
|
||||
@@ -2962,20 +2979,6 @@ const message = {
|
||||
remote: 'Remote SSH',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: 'Cache settings',
|
||||
tabs: 'Tab cache',
|
||||
pagination: 'Pagination cache',
|
||||
refresh: 'Refresh interval cache',
|
||||
search: 'Search input cache',
|
||||
duration: 'Cache duration',
|
||||
'24h': '24h',
|
||||
week: 'One week',
|
||||
month: 'One month',
|
||||
forever: 'Forever',
|
||||
helper: 'Expires after the last state change. Expired values return to page defaults.',
|
||||
},
|
||||
|
||||
panel: 'Panel',
|
||||
user: 'Panel user',
|
||||
userChange: 'Change panel user',
|
||||
@@ -3698,6 +3701,7 @@ const message = {
|
||||
link: 'Link',
|
||||
showCurrentArch: 'Architecture',
|
||||
syncLocalApp: 'Sync Local App',
|
||||
localAppSyncToNodes: 'Include child nodes when syncing local apps',
|
||||
memoryRequiredHelper: 'Current application memory requirement {0}',
|
||||
gpuConfig: 'Enable GPU Support',
|
||||
gpuConfigHelper:
|
||||
@@ -4313,9 +4317,14 @@ const message = {
|
||||
familyIncomplete: '{0} chains are incomplete',
|
||||
resetWithBackup: 'Back up rules and reset',
|
||||
resetOnly: 'Reset only',
|
||||
resetWithBackupHelper: 'Save the current module rules as JSON for restoration during initialization.',
|
||||
resetWithBackupHelper:
|
||||
'Save the current module rules as JSON for restoration during initialization or startup.',
|
||||
resetOnlyHelper: 'Clear current rules without creating a new backup. Existing backups are kept.',
|
||||
backupDirectoryHelper: 'Valid rule backups in {0} are listed below. The newest file is selected by default.',
|
||||
startOnly: 'Start only',
|
||||
startAndRestore: 'Start and restore from backup',
|
||||
startWithBackupHelper:
|
||||
'Start the firewall normally, or choose an existing JSON backup to start and restore rules.',
|
||||
restoreAllRules: 'Initialize and restore rules',
|
||||
importRuleBackup: 'Import from backup',
|
||||
initializeOnly: 'Initialize only',
|
||||
|
||||
@@ -776,6 +776,15 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
'Introduzca la IPv4 o el nombre de host usado en el navegador, sin protocolo, puerto ni ruta. HTTPS usa el certificado local de la aplicación.',
|
||||
deepSeekAuthHelper:
|
||||
'Usuario: letras, números, punto, guion bajo o guion. Contraseña: al menos 12 caracteres, sin espacios al inicio o al final ni saltos de línea.',
|
||||
octopAuthHelper:
|
||||
'Contraseña: al menos 8 caracteres con letras y números; evite contraseñas comunes. Si se rechaza al iniciar, consulte las instrucciones en los registros de la aplicación.',
|
||||
initialCredentials: 'Credenciales iniciales; si las cambió en la aplicación, use la contraseña actual.',
|
||||
nativeAgentAccessUnavailable:
|
||||
'La instalación existente no tiene una dirección o puerto HTTPS compatible. Revise la configuración de la aplicación.',
|
||||
agent: 'Agente',
|
||||
account: 'Cuenta de modelo',
|
||||
batchOperation: 'Operación por lotes',
|
||||
@@ -1526,6 +1535,14 @@ const message = {
|
||||
extraHeaders: 'Encabezados adicionales',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Concurrencia actual',
|
||||
sampleMoment: 'Al tomar la muestra',
|
||||
sampleWindow: 'Intervalo de muestreo',
|
||||
requestUnit: 'solicitudes',
|
||||
processing: 'En proceso',
|
||||
queued: 'En cola',
|
||||
requestTiming: 'Tiempo de las solicitudes',
|
||||
completedRequests: 'Solicitudes completadas en el intervalo',
|
||||
tokensPerSecond: 'tokens/s',
|
||||
requestsPerSecond: 'Solicitudes/s',
|
||||
finishReasonLabels: {
|
||||
@@ -3007,20 +3024,6 @@ const message = {
|
||||
remote: 'SSH remoto',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: 'Configuración de caché',
|
||||
tabs: 'Caché de pestañas',
|
||||
pagination: 'Caché de paginación',
|
||||
refresh: 'Caché del intervalo de actualización',
|
||||
search: 'Caché del campo de búsqueda',
|
||||
duration: 'Duración de la caché',
|
||||
'24h': '24 horas',
|
||||
week: 'Una semana',
|
||||
month: 'Un mes',
|
||||
forever: 'Permanente',
|
||||
helper: 'El plazo se calcula desde el último cambio de estado. Al caducar, se restablecen los valores predeterminados.',
|
||||
},
|
||||
|
||||
panel: 'Panel',
|
||||
user: 'Usuario del panel',
|
||||
userChange: 'Cambiar usuario del panel',
|
||||
@@ -3765,6 +3768,7 @@ const message = {
|
||||
link: 'Enlace',
|
||||
showCurrentArch: 'Arquitectura',
|
||||
syncLocalApp: 'Sincronizar app local',
|
||||
localAppSyncToNodes: 'Incluir nodos secundarios al sincronizar apps locales',
|
||||
memoryRequiredHelper: 'La app requiere {0} de memoria',
|
||||
gpuConfig: 'Habilitar soporte GPU',
|
||||
gpuConfigHelper: 'Asegúrese de tener GPU NVIDIA, drivers y NVIDIA Docker Container Toolkit instalados',
|
||||
@@ -4361,10 +4365,14 @@ const message = {
|
||||
resetWithBackup: 'Guardar reglas y restablecer',
|
||||
resetOnly: 'Solo restablecer',
|
||||
resetWithBackupHelper:
|
||||
'Guarda las reglas del módulo actual en JSON para restaurarlas durante la inicialización.',
|
||||
'Guarde las reglas del módulo actual en JSON para restaurarlas al inicializar o iniciar el cortafuegos.',
|
||||
resetOnlyHelper: 'Borrar las reglas actuales sin crear una copia nueva. Se conservan las copias existentes.',
|
||||
backupDirectoryHelper:
|
||||
'Se muestran las copias válidas de reglas en {0}. Se selecciona la más reciente por defecto.',
|
||||
startOnly: 'Solo iniciar',
|
||||
startAndRestore: 'Iniciar y restaurar desde una copia de seguridad',
|
||||
startWithBackupHelper:
|
||||
'Inicie el cortafuegos normalmente o elija una copia JSON existente para iniciar y restaurar reglas.',
|
||||
restoreAllRules: 'Inicializar y restaurar reglas',
|
||||
importRuleBackup: 'Importar desde copia de seguridad',
|
||||
initializeOnly: 'Solo inicializar',
|
||||
|
||||
@@ -768,6 +768,14 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
'نشانی IPv4 یا نام میزبان مورد استفاده در مرورگر را بدون پروتکل، درگاه یا مسیر وارد کنید. HTTPS از گواهی محلی برنامه استفاده میکند.',
|
||||
deepSeekAuthHelper:
|
||||
'نام کاربری: حروف لاتین، اعداد، نقطه، زیرخط یا خط تیره. گذرواژه: حداقل ۱۲ نویسه، بدون فاصله در ابتدا و انتها یا شکست خط.',
|
||||
octopAuthHelper:
|
||||
'گذرواژه: حداقل ۸ نویسه شامل حروف و اعداد؛ از گذرواژههای رایج پرهیز کنید. اگر رد شد، راهنمای تنظیمات را در گزارشهای برنامه ببینید.',
|
||||
initialCredentials: 'اطلاعات ورود اولیه؛ اگر در برنامه تغییر کرده، از گذرواژه فعلی استفاده کنید.',
|
||||
nativeAgentAccessUnavailable: 'نصب موجود نشانی یا درگاه HTTPS سازگار ندارد. تنظیمات برنامه را بررسی کنید.',
|
||||
agent: 'عامل',
|
||||
account: 'حساب مدل',
|
||||
batchOperation: 'عملیات دستهای',
|
||||
@@ -1502,6 +1510,14 @@ const message = {
|
||||
extraHeaders: 'هدرهای اضافی',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'درخواستهای همزمان فعلی',
|
||||
sampleMoment: 'در لحظه نمونهبرداری',
|
||||
sampleWindow: 'بازه نمونهبرداری',
|
||||
requestUnit: 'درخواست',
|
||||
processing: 'در حال پردازش',
|
||||
queued: 'در صف',
|
||||
requestTiming: 'زمان پردازش درخواست',
|
||||
completedRequests: 'تعداد درخواستهای تکمیلشده در بازه',
|
||||
tokensPerSecond: 'tokens/ثانیه',
|
||||
requestsPerSecond: 'درخواست/ثانیه',
|
||||
finishReasonLabels: {
|
||||
@@ -2932,20 +2948,6 @@ const message = {
|
||||
remote: 'SSH از راه دور',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: 'تنظیمات حافظهٔ نهان',
|
||||
tabs: 'حافظهٔ نهان زبانهها',
|
||||
pagination: 'حافظهٔ نهان صفحهبندی',
|
||||
refresh: 'حافظهٔ نهان فاصلهٔ بازخوانی',
|
||||
search: 'حافظهٔ نهان ورودی جستجو',
|
||||
duration: 'مدت نگهداری حافظهٔ نهان',
|
||||
'24h': '۲۴ ساعت',
|
||||
week: 'یک هفته',
|
||||
month: 'یک ماه',
|
||||
forever: 'دائمی',
|
||||
helper: 'مدت اعتبار از آخرین تغییر وضعیت محاسبه میشود. پس از انقضا، مقادیر پیشفرض بازیابی میشوند.',
|
||||
},
|
||||
|
||||
panel: 'پنل',
|
||||
user: 'کاربر پنل',
|
||||
userChange: 'تغییر کاربر پنل',
|
||||
@@ -3664,6 +3666,7 @@ const message = {
|
||||
link: 'لینک',
|
||||
showCurrentArch: 'معماری',
|
||||
syncLocalApp: 'همگامسازی برنامه محلی',
|
||||
localAppSyncToNodes: 'همگامسازی گرههای فرزند هنگام همگامسازی برنامههای محلی',
|
||||
memoryRequiredHelper: 'نیاز حافظه برنامه فعلی {0}',
|
||||
gpuConfig: 'فعالسازی پشتیبانی GPU',
|
||||
gpuConfigHelper:
|
||||
@@ -4273,10 +4276,15 @@ const message = {
|
||||
familyIncomplete: 'زنجیرههای {0} ناقص هستند',
|
||||
resetWithBackup: 'پشتیبانگیری قوانین و بازنشانی',
|
||||
resetOnly: 'فقط بازنشانی',
|
||||
resetWithBackupHelper: 'قوانین ماژول فعلی را بهصورت JSON ذخیره کنید تا هنگام مقداردهی اولیه بازیابی شوند.',
|
||||
resetWithBackupHelper:
|
||||
'قوانین ماژول فعلی را بهصورت JSON ذخیره کنید تا هنگام مقداردهی اولیه یا راهاندازی بازیابی شوند.',
|
||||
resetOnlyHelper: 'قوانین فعلی بدون ایجاد نسخه پشتیبان جدید پاک میشوند. نسخههای پشتیبان قبلی حفظ میشوند.',
|
||||
backupDirectoryHelper:
|
||||
'نسخههای پشتیبان معتبر قوانین در {0} نمایش داده میشوند. جدیدترین فایل بهطور پیشفرض انتخاب میشود.',
|
||||
startOnly: 'فقط راه\u200cاندازی',
|
||||
startAndRestore: 'راه\u200cاندازی و بازیابی از نسخه پشتیبان',
|
||||
startWithBackupHelper:
|
||||
'فایروال را بهطور معمول راهاندازی کنید یا یک نسخه پشتیبان JSON موجود را برای راهاندازی و بازیابی قوانین انتخاب کنید.',
|
||||
restoreAllRules: 'مقداردهی اولیه و بازیابی قوانین',
|
||||
importRuleBackup: 'ورود از نسخه پشتیبان',
|
||||
initializeOnly: 'فقط راهاندازی',
|
||||
|
||||
@@ -772,6 +772,15 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
'ブラウザーで使用する IPv4 アドレスまたはホスト名を、スキーム・ポート・パスなしで入力してください。HTTPS はアプリのローカル証明書を使用します。',
|
||||
deepSeekAuthHelper:
|
||||
'ユーザー名には英数字、ドット、アンダースコア、ハイフンを使用できます。パスワードは12文字以上で、前後の空白や改行を含めないでください。',
|
||||
octopAuthHelper:
|
||||
'パスワードは英字と数字を含む8文字以上とし、よく使われるパスワードを避けてください。初期化時に拒否された場合は、アプリのログで設定手順を確認してください。',
|
||||
initialCredentials: '初期ログイン情報です。アプリ内で変更した場合は現在のパスワードを使用してください。',
|
||||
nativeAgentAccessUnavailable:
|
||||
'既存のインストールに対応する HTTPS アクセス先またはポートがありません。アプリの設定を確認してください。',
|
||||
agent: 'エージェント',
|
||||
account: 'モデルアカウント',
|
||||
batchOperation: '一括操作',
|
||||
@@ -1505,6 +1514,14 @@ const message = {
|
||||
extraHeaders: '追加ヘッダー',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: '現在の同時リクエスト',
|
||||
sampleMoment: 'サンプリング時点',
|
||||
sampleWindow: '集計区間',
|
||||
requestUnit: '件',
|
||||
processing: '処理中',
|
||||
queued: '待機中',
|
||||
requestTiming: 'リクエスト処理時間',
|
||||
completedRequests: '区間内の完了リクエスト数',
|
||||
tokensPerSecond: 'tokens/秒',
|
||||
requestsPerSecond: 'リクエスト/秒',
|
||||
finishReasonLabels: {
|
||||
@@ -2937,20 +2954,6 @@ const message = {
|
||||
remote: 'リモートSSH',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: 'キャッシュ設定',
|
||||
tabs: 'タブのキャッシュ',
|
||||
pagination: 'ページネーションのキャッシュ',
|
||||
refresh: '更新間隔のキャッシュ',
|
||||
search: '検索入力のキャッシュ',
|
||||
duration: 'キャッシュ期間',
|
||||
'24h': '24時間',
|
||||
week: '1週間',
|
||||
month: '1か月',
|
||||
forever: '無期限',
|
||||
helper: '有効期間は状態を最後に変更した時点から計算されます。期限が切れると既定値に戻ります。',
|
||||
},
|
||||
|
||||
panel: 'パネル',
|
||||
user: 'パネルユーザー',
|
||||
userChange: 'パネルユーザーを変更します',
|
||||
@@ -3684,6 +3687,7 @@ const message = {
|
||||
link: 'リンク',
|
||||
showCurrentArch: '現在のサーバーアーキテクチャのアプリケーション',
|
||||
syncLocalApp: 'ローカルアプリの同期',
|
||||
localAppSyncToNodes: 'ローカルアプリ同期時に子ノードも同期',
|
||||
memoryRequiredHelper: '現在のアプリケーションは {0} メモリが必要です',
|
||||
gpuConfig: 'GPUサポートを有効化',
|
||||
gpuConfigHelper:
|
||||
@@ -4296,9 +4300,12 @@ const message = {
|
||||
familyIncomplete: '{0} のチェーンが不完全です',
|
||||
resetWithBackup: 'ルールをバックアップしてリセット',
|
||||
resetOnly: 'リセットのみ',
|
||||
resetWithBackupHelper: '現在のモジュールのルールを JSON に保存し、初期化時に復元できます。',
|
||||
resetWithBackupHelper: '現在のモジュールのルールを JSON に保存し、初期化時または起動時に復元できます。',
|
||||
resetOnlyHelper: '新しいバックアップを作成せずに現在のルールを消去します。既存のバックアップは保持されます。',
|
||||
backupDirectoryHelper: '{0} 内の有効なルールバックアップを表示します。既定では最新のファイルが選択されます。',
|
||||
startOnly: '起動のみ',
|
||||
startAndRestore: '起動してバックアップから復元',
|
||||
startWithBackupHelper: '通常起動するか、既存の JSON バックアップを選んで起動とルールの復元を行います。',
|
||||
restoreAllRules: '初期化してルールを復元',
|
||||
importRuleBackup: 'バックアップからインポート',
|
||||
initializeOnly: '初期化のみ',
|
||||
|
||||
@@ -763,6 +763,15 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
'브라우저에서 사용하는 IPv4 주소 또는 호스트 이름을 입력하세요. 프로토콜, 포트, 경로는 제외하세요. HTTPS는 앱의 로컬 인증서를 사용합니다.',
|
||||
deepSeekAuthHelper:
|
||||
'사용자 이름은 영문, 숫자, 점, 밑줄, 하이픈만 지원합니다. 비밀번호는 12자 이상이며 앞뒤 공백이나 줄바꿈을 포함할 수 없습니다.',
|
||||
octopAuthHelper:
|
||||
'비밀번호는 문자와 숫자를 포함한 8자 이상이어야 하며 흔한 비밀번호는 피하세요. 초기화 시 거부되면 앱 로그의 설정 안내를 확인하세요.',
|
||||
initialCredentials: '초기 로그인 정보입니다. 앱에서 변경했다면 현재 비밀번호를 사용하세요.',
|
||||
nativeAgentAccessUnavailable:
|
||||
'기존 설치에 호환되는 HTTPS 접속 주소 또는 포트가 없습니다. 앱 설정을 확인하세요.',
|
||||
agent: '에이전트',
|
||||
account: '모델 계정',
|
||||
batchOperation: '일괄 작업',
|
||||
@@ -1490,6 +1499,14 @@ const message = {
|
||||
extraHeaders: '추가 헤더',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: '현재 동시 요청',
|
||||
sampleMoment: '샘플링 시점',
|
||||
sampleWindow: '집계 구간',
|
||||
requestUnit: '개 요청',
|
||||
processing: '처리 중',
|
||||
queued: '대기 중',
|
||||
requestTiming: '요청 소요 시간',
|
||||
completedRequests: '구간 내 완료 요청 수',
|
||||
tokensPerSecond: 'tokens/초',
|
||||
requestsPerSecond: '요청/초',
|
||||
finishReasonLabels: {
|
||||
@@ -2898,20 +2915,6 @@ const message = {
|
||||
remote: '원격 SSH',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: '캐시 설정',
|
||||
tabs: '탭 캐시',
|
||||
pagination: '페이지 이동 캐시',
|
||||
refresh: '새로 고침 간격 캐시',
|
||||
search: '검색 입력 캐시',
|
||||
duration: '캐시 유지 기간',
|
||||
'24h': '24시간',
|
||||
week: '1주',
|
||||
month: '1개월',
|
||||
forever: '무기한',
|
||||
helper: '유효 기간은 상태를 마지막으로 변경한 시점부터 계산됩니다. 만료되면 기본값으로 돌아갑니다.',
|
||||
},
|
||||
|
||||
panel: '패널',
|
||||
user: '패널 사용자',
|
||||
userChange: '패널 사용자 변경',
|
||||
@@ -3619,6 +3622,7 @@ const message = {
|
||||
link: '링크',
|
||||
showCurrentArch: '현재 서버 아키텍처 애플리케이션',
|
||||
syncLocalApp: '로컬 애플리케이션 동기화',
|
||||
localAppSyncToNodes: '로컬 앱 동기화 시 하위 노드도 동기화',
|
||||
memoryRequiredHelper: '현재 애플리케이션은 {0} 메모리가 필요합니다',
|
||||
gpuConfig: 'GPU 지원 활성화',
|
||||
gpuConfigHelper:
|
||||
@@ -4218,9 +4222,12 @@ const message = {
|
||||
familyIncomplete: '{0} 체인이 불완전합니다',
|
||||
resetWithBackup: '규칙 백업 후 초기화',
|
||||
resetOnly: '초기화만 수행',
|
||||
resetWithBackupHelper: '현재 모듈의 규칙을 JSON으로 저장하여 초기화 시 복원할 수 있습니다.',
|
||||
resetWithBackupHelper: '현재 모듈의 규칙을 JSON으로 저장하여 초기화 또는 시작 시 복원할 수 있습니다.',
|
||||
resetOnlyHelper: '새 백업 없이 현재 규칙을 삭제합니다. 기존 백업은 유지됩니다.',
|
||||
backupDirectoryHelper: '{0}의 유효한 규칙 백업을 표시합니다. 기본적으로 최신 파일이 선택됩니다.',
|
||||
startOnly: '시작만',
|
||||
startAndRestore: '시작 및 백업에서 복원',
|
||||
startWithBackupHelper: '방화벽을 일반적으로 시작하거나 기존 JSON 백업을 선택하여 시작하고 규칙을 복원합니다.',
|
||||
restoreAllRules: '초기화 및 규칙 복원',
|
||||
importRuleBackup: '백업에서 가져오기',
|
||||
initializeOnly: '초기화만 수행',
|
||||
|
||||
@@ -761,6 +761,15 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
'ປ້ອນທີ່ຢູ່ IPv4 ຫຼືຊື່ໂຮສທີ່ໃຊ້ໃນບຣາວເຊີ ໂດຍບໍ່ມີໂປຣໂຕຄອນ, ພອດ ຫຼືເສັ້ນທາງ. HTTPS ໃຊ້ໃບຢັ້ງຢືນພາຍໃນຂອງແອັບ.',
|
||||
deepSeekAuthHelper:
|
||||
'ຊື່ຜູ້ໃຊ້: ຕົວອັກສອນອັງກິດ, ຕົວເລກ, ຈຸດ, ຂີດລຸ່ມ ຫຼືຂີດກາງ. ລະຫັດຜ່ານ: ຢ່າງໜ້ອຍ 12 ຕົວ ໂດຍບໍ່ມີຊ່ອງຫວ່າງຢູ່ຫົວ/ທ້າຍ ຫຼືຂຶ້ນແຖວໃໝ່.',
|
||||
octopAuthHelper:
|
||||
'ລະຫັດຜ່ານ: ຢ່າງໜ້ອຍ 8 ຕົວ ປະກອບດ້ວຍຕົວອັກສອນແລະຕົວເລກ; ຫຼີກລ້ຽງລະຫັດທີ່ໃຊ້ທົ່ວໄປ. ຖ້າຖືກປະຕິເສດ ໃຫ້ເບິ່ງຄຳແນະນຳໃນບັນທຶກຂອງແອັບ.',
|
||||
initialCredentials: 'ຂໍ້ມູນເຂົ້າລະບົບເບື້ອງຕົ້ນ; ຖ້າປ່ຽນໃນແອັບແລ້ວ ໃຫ້ໃຊ້ລະຫັດປັດຈຸບັນ.',
|
||||
nativeAgentAccessUnavailable:
|
||||
'ການຕິດຕັ້ງທີ່ມີຢູ່ບໍ່ມີທີ່ຢູ່ ຫຼືພອດ HTTPS ທີ່ເຂົ້າກັນໄດ້. ກະລຸນາກວດສອບການຕັ້ງຄ່າແອັບ.',
|
||||
agent: 'ຕົວແທນ (Agent)',
|
||||
account: 'ບັນຊີໂມເດວ',
|
||||
batchOperation: 'ດຳເນີນການແບບກຸ່ມ',
|
||||
@@ -1484,6 +1493,14 @@ const message = {
|
||||
extraHeaders: 'Headers ເພີ່ມເຕີມ',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'ຄຳຮ້ອງຂໍພ້ອມກັນໃນປັດຈຸບັນ',
|
||||
sampleMoment: 'ໃນເວລາເກັບຕົວຢ່າງ',
|
||||
sampleWindow: 'ຊ່ວງເວລາເກັບຕົວຢ່າງ',
|
||||
requestUnit: 'ຄຳຮ້ອງຂໍ',
|
||||
processing: 'ກຳລັງປະມວນຜົນ',
|
||||
queued: 'ກຳລັງລໍຖ້າ',
|
||||
requestTiming: 'ເວລາປະມວນຜົນຄຳຮ້ອງຂໍ',
|
||||
completedRequests: 'ຈຳນວນຄຳຮ້ອງຂໍທີ່ສຳເລັດໃນຊ່ວງເວລາ',
|
||||
tokensPerSecond: 'tokens/ວິນາທີ',
|
||||
requestsPerSecond: 'ຄຳຮ້ອງຂໍ/ວິນາທີ',
|
||||
finishReasonLabels: {
|
||||
@@ -2879,20 +2896,6 @@ const message = {
|
||||
remote: 'SSH ທາງໄກ',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: 'ການຕັ້ງຄ່າແຄດ',
|
||||
tabs: 'ແຄດແຖບ',
|
||||
pagination: 'ແຄດການແບ່ງໜ້າ',
|
||||
refresh: 'ແຄດຊ່ວງເວລາຣີເຟຣຊ',
|
||||
search: 'ແຄດຂໍ້ຄວາມຄົ້ນຫາ',
|
||||
duration: 'ໄລຍະເວລາເກັບແຄດ',
|
||||
'24h': '24 ຊົ່ວໂມງ',
|
||||
week: 'ໜຶ່ງອາທິດ',
|
||||
month: 'ໜຶ່ງເດືອນ',
|
||||
forever: 'ຖາວອນ',
|
||||
helper: 'ໄລຍະເວລາຈະນັບຈາກການປ່ຽນສະຖານະຄັ້ງຫຼ້າສຸດ. ເມື່ອໝົດອາຍຸ ຈະກັບຄືນເປັນຄ່າເລີ່ມຕົ້ນ.',
|
||||
},
|
||||
|
||||
panel: 'ພາເນລ',
|
||||
user: 'ຜູ້ໃຊ້ພາເນລ',
|
||||
userChange: 'ປ່ຽນຜູ້ໃຊ້ພາເນລ',
|
||||
@@ -3596,6 +3599,7 @@ const message = {
|
||||
link: 'ລິ້ງ',
|
||||
showCurrentArch: 'ສະຖາປັດຕະຍະກຳ',
|
||||
syncLocalApp: 'ຊິງຄ໌ແອັບໃນເຄື່ອງ',
|
||||
localAppSyncToNodes: 'ຮວມໂນດລູກເມື່ອຊິງຄ໌ແອັບໃນເຄື່ອງ',
|
||||
memoryRequiredHelper: 'ຄວາມຕ້ອງການໜ່ວຍຄວາມຈຳຂອງແອັບປະຈຸບັນ {0}',
|
||||
gpuConfig: 'ເປີດໃຊ້ການຮອງຮັບ GPU',
|
||||
gpuConfigHelper:
|
||||
@@ -4187,9 +4191,12 @@ const message = {
|
||||
familyIncomplete: 'ຊຸດກົດ {0} ບໍ່ຄົບຖ້ວນ',
|
||||
resetWithBackup: 'ສຳຮອງກົດແລ້ວຣີເຊັດ',
|
||||
resetOnly: 'ຣີເຊັດເທົ່ານັ້ນ',
|
||||
resetWithBackupHelper: 'ບັນທຶກກົດຂອງໂມດູນປັດຈຸບັນເປັນ JSON ເພື່ອກູ້ຄືນເມື່ອເລີ່ມຕົ້ນ.',
|
||||
resetWithBackupHelper: 'ບັນທຶກກົດຂອງໂມດູນປັດຈຸບັນເປັນ JSON ເພື່ອກູ້ຄືນເມື່ອເລີ່ມຕົ້ນ ຫຼື ເປີດໃຊ້ໄຟວໍ.',
|
||||
resetOnlyHelper: 'ລຶບກົດປັດຈຸບັນໂດຍບໍ່ສ້າງໄຟລ໌ສຳຮອງໃໝ່. ໄຟລ໌ສຳຮອງເກົ່າຍັງຄົງຢູ່.',
|
||||
backupDirectoryHelper: 'ສະແດງໄຟລ໌ສຳຮອງກົດທີ່ໃຊ້ໄດ້ໃນ {0}. ເລືອກໄຟລ໌ຫຼ້າສຸດເປັນຄ່າເລີ່ມຕົ້ນ.',
|
||||
startOnly: 'ເລີ່ມຕົ້ນເທົ່ານັ້ນ',
|
||||
startAndRestore: 'ເລີ່ມຕົ້ນ ແລະ ກູ້ຄືນຈາກຂໍ້ມູນສຳຮອງ',
|
||||
startWithBackupHelper: 'ເປີດໄຟວໍຕາມປົກກະຕິ ຫຼື ເລືອກຂໍ້ມູນສຳຮອງ JSON ທີ່ມີຢູ່ເພື່ອເລີ່ມຕົ້ນ ແລະ ກູ້ຄືນກົດ.',
|
||||
restoreAllRules: 'ເລີ່ມຕົ້ນ ແລະ ກູ້ຄືນກົດ',
|
||||
importRuleBackup: 'ນຳເຂົ້າຈາກຂໍ້ມູນສຳຮອງ',
|
||||
initializeOnly: 'ເລີ່ມຕົ້ນເທົ່ານັ້ນ',
|
||||
|
||||
@@ -781,6 +781,15 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
'Masukkan alamat IPv4 atau nama hos yang digunakan dalam pelayar, tanpa protokol, port atau laluan. HTTPS menggunakan sijil setempat aplikasi.',
|
||||
deepSeekAuthHelper:
|
||||
'Nama pengguna: huruf, nombor, titik, garis bawah atau sempang. Kata laluan: sekurang-kurangnya 12 aksara, tanpa ruang di awal/akhir atau baris baharu.',
|
||||
octopAuthHelper:
|
||||
'Kata laluan: sekurang-kurangnya 8 aksara dengan huruf dan nombor; elakkan kata laluan lazim. Jika ditolak, semak arahan persediaan dalam log aplikasi.',
|
||||
initialCredentials: 'Maklumat log masuk awal; gunakan kata laluan semasa jika telah diubah dalam aplikasi.',
|
||||
nativeAgentAccessUnavailable:
|
||||
'Pemasangan sedia ada tiada alamat atau port HTTPS yang serasi. Semak tetapan aplikasi.',
|
||||
agent: 'Agen',
|
||||
account: 'Akaun model',
|
||||
batchOperation: 'Operasi pukal',
|
||||
@@ -1531,6 +1540,14 @@ const message = {
|
||||
extraHeaders: 'Pengepala Tambahan',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Permintaan serentak semasa',
|
||||
sampleMoment: 'Pada masa pensampelan',
|
||||
sampleWindow: 'Selang pensampelan',
|
||||
requestUnit: 'permintaan',
|
||||
processing: 'Sedang diproses',
|
||||
queued: 'Dalam baris gilir',
|
||||
requestTiming: 'Tempoh permintaan',
|
||||
completedRequests: 'Permintaan selesai dalam selang masa',
|
||||
tokensPerSecond: 'tokens/s',
|
||||
requestsPerSecond: 'Permintaan/s',
|
||||
finishReasonLabels: {
|
||||
@@ -3005,20 +3022,6 @@ const message = {
|
||||
remote: 'SSH jauh',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: 'Tetapan cache',
|
||||
tabs: 'Cache tab',
|
||||
pagination: 'Cache halaman',
|
||||
refresh: 'Cache sela penyegaran',
|
||||
search: 'Cache input carian',
|
||||
duration: 'Tempoh cache',
|
||||
'24h': '24 jam',
|
||||
week: 'Satu minggu',
|
||||
month: 'Satu bulan',
|
||||
forever: 'Kekal',
|
||||
helper: 'Tempoh sah dikira dari perubahan keadaan terakhir. Selepas tamat tempoh, nilai lalai dipulihkan.',
|
||||
},
|
||||
|
||||
panel: 'Panel',
|
||||
user: 'Pengguna panel',
|
||||
userChange: 'Tukar pengguna panel',
|
||||
@@ -3756,6 +3759,7 @@ const message = {
|
||||
link: 'Pautan',
|
||||
showCurrentArch: 'Aplikasi seni bina pelayan semasa',
|
||||
syncLocalApp: 'Segerakkan Aplikasi Tempatan',
|
||||
localAppSyncToNodes: 'Sertakan nod anak semasa menyegerakkan aplikasi tempatan',
|
||||
memoryRequiredHelper: 'Aplikasi semasa memerlukan {0} memori',
|
||||
gpuConfig: 'Aktifkan Sokongan GPU',
|
||||
gpuConfigHelper:
|
||||
@@ -4376,10 +4380,15 @@ const message = {
|
||||
familyIncomplete: 'Rantaian {0} tidak lengkap',
|
||||
resetWithBackup: 'Sandarkan peraturan dan tetapkan semula',
|
||||
resetOnly: 'Tetapkan semula sahaja',
|
||||
resetWithBackupHelper: 'Simpan peraturan modul semasa sebagai JSON untuk dipulihkan semasa pemulaan.',
|
||||
resetWithBackupHelper:
|
||||
'Simpan peraturan modul semasa sebagai JSON untuk dipulihkan semasa pemulaan atau semasa tembok api dimulakan.',
|
||||
resetOnlyHelper: 'Padam peraturan semasa tanpa sandaran baharu. Sandaran sedia ada dikekalkan.',
|
||||
backupDirectoryHelper:
|
||||
'Sandaran peraturan yang sah dalam {0} disenaraikan di bawah. Fail terkini dipilih secara lalai.',
|
||||
startOnly: 'Mula sahaja',
|
||||
startAndRestore: 'Mula dan pulihkan daripada sandaran',
|
||||
startWithBackupHelper:
|
||||
'Mulakan tembok api seperti biasa atau pilih sandaran JSON sedia ada untuk memulakan dan memulihkan peraturan.',
|
||||
restoreAllRules: 'Mulakan dan pulihkan peraturan',
|
||||
importRuleBackup: 'Import daripada sandaran',
|
||||
initializeOnly: 'Mulakan sahaja',
|
||||
|
||||
@@ -774,6 +774,15 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
'Informe o IPv4 ou nome do host usado no navegador, sem protocolo, porta ou caminho. O HTTPS usa o certificado local do aplicativo.',
|
||||
deepSeekAuthHelper:
|
||||
'Usuário: letras, números, ponto, sublinhado ou hífen. Senha: no mínimo 12 caracteres, sem espaços nas extremidades ou quebras de linha.',
|
||||
octopAuthHelper:
|
||||
'Senha: no mínimo 8 caracteres com letras e números; evite senhas comuns. Se rejeitada na inicialização, consulte as instruções nos logs do aplicativo.',
|
||||
initialCredentials: 'Credenciais iniciais; se alteradas no aplicativo, use a senha atual.',
|
||||
nativeAgentAccessUnavailable:
|
||||
'A instalação existente não tem endereço ou porta HTTPS compatível. Verifique as configurações do aplicativo.',
|
||||
agent: 'Agente',
|
||||
account: 'Conta de modelo',
|
||||
batchOperation: 'Operação em lote',
|
||||
@@ -1527,6 +1536,14 @@ const message = {
|
||||
extraHeaders: 'Cabeçalhos extras',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Concorrência atual',
|
||||
sampleMoment: 'No momento da coleta',
|
||||
sampleWindow: 'Intervalo de coleta',
|
||||
requestUnit: 'solicitações',
|
||||
processing: 'Em processamento',
|
||||
queued: 'Na fila',
|
||||
requestTiming: 'Tempo das solicitações',
|
||||
completedRequests: 'Solicitações concluídas no intervalo',
|
||||
tokensPerSecond: 'tokens/s',
|
||||
requestsPerSecond: 'Solicitações/s',
|
||||
finishReasonLabels: {
|
||||
@@ -3004,20 +3021,6 @@ const message = {
|
||||
remote: 'SSH remoto',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: 'Configurações de cache',
|
||||
tabs: 'Cache de abas',
|
||||
pagination: 'Cache de paginação',
|
||||
refresh: 'Cache do intervalo de atualização',
|
||||
search: 'Cache do campo de pesquisa',
|
||||
duration: 'Duração do cache',
|
||||
'24h': '24 horas',
|
||||
week: 'Uma semana',
|
||||
month: 'Um mês',
|
||||
forever: 'Permanente',
|
||||
helper: 'O prazo é contado a partir da última alteração de estado. Após a expiração, os valores padrão são restaurados.',
|
||||
},
|
||||
|
||||
panel: 'Painel',
|
||||
user: 'Usuário do painel',
|
||||
userChange: 'Alterar usuário do painel',
|
||||
@@ -3772,6 +3775,7 @@ const message = {
|
||||
link: 'Link',
|
||||
showCurrentArch: 'Aplicações da arquitetura atual do servidor',
|
||||
syncLocalApp: 'Sincronizar Aplicativo Local',
|
||||
localAppSyncToNodes: 'Incluir nós filhos ao sincronizar apps locais',
|
||||
memoryRequiredHelper: 'O aplicativo atual requer {0} de memória',
|
||||
gpuConfig: 'Ativar Suporte a GPU',
|
||||
gpuConfigHelper:
|
||||
@@ -4398,10 +4402,15 @@ const message = {
|
||||
familyIncomplete: 'As cadeias de {0} estão incompletas',
|
||||
resetWithBackup: 'Salvar regras e redefinir',
|
||||
resetOnly: 'Apenas redefinir',
|
||||
resetWithBackupHelper: 'Salve as regras do módulo atual em JSON para restaurá-las durante a inicialização.',
|
||||
resetWithBackupHelper:
|
||||
'Salve as regras do módulo atual em JSON para restaurá-las ao inicializar ou iniciar o firewall.',
|
||||
resetOnlyHelper: 'Limpar as regras atuais sem criar um novo backup. Os backups existentes são mantidos.',
|
||||
backupDirectoryHelper:
|
||||
'Os backups válidos de regras em {0} são listados abaixo. O mais recente é selecionado por padrão.',
|
||||
startOnly: 'Apenas iniciar',
|
||||
startAndRestore: 'Iniciar e restaurar do backup',
|
||||
startWithBackupHelper:
|
||||
'Inicie o firewall normalmente ou escolha um backup JSON existente para iniciar e restaurar regras.',
|
||||
restoreAllRules: 'Inicializar e restaurar regras',
|
||||
importRuleBackup: 'Importar do backup',
|
||||
initializeOnly: 'Apenas inicializar',
|
||||
|
||||
@@ -772,6 +772,15 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
'Введите IPv4-адрес или имя хоста, используемое в браузере, без протокола, порта и пути. HTTPS использует локальный сертификат приложения.',
|
||||
deepSeekAuthHelper:
|
||||
'Имя пользователя: латинские буквы, цифры, точка, подчёркивание или дефис. Пароль: минимум 12 символов, без пробелов по краям и переводов строк.',
|
||||
octopAuthHelper:
|
||||
'Пароль: минимум 8 символов с буквами и цифрами; избегайте распространённых паролей. Если пароль отклонён, инструкции доступны в журнале приложения.',
|
||||
initialCredentials: 'Начальные учётные данные; если пароль изменён в приложении, используйте текущий.',
|
||||
nativeAgentAccessUnavailable:
|
||||
'У существующей установки нет совместимого HTTPS-адреса или порта. Проверьте настройки приложения.',
|
||||
agent: 'Агент',
|
||||
account: 'Аккаунт модели',
|
||||
batchOperation: 'Пакетная операция',
|
||||
@@ -1520,6 +1529,14 @@ const message = {
|
||||
extraHeaders: 'Дополнительные заголовки',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Текущие параллельные запросы',
|
||||
sampleMoment: 'На момент замера',
|
||||
sampleWindow: 'Интервал замера',
|
||||
requestUnit: 'запросов',
|
||||
processing: 'В обработке',
|
||||
queued: 'В очереди',
|
||||
requestTiming: 'Время обработки запросов',
|
||||
completedRequests: 'Завершённые запросы за интервал',
|
||||
tokensPerSecond: 'tokens/с',
|
||||
requestsPerSecond: 'Запросов/с',
|
||||
finishReasonLabels: {
|
||||
@@ -2977,20 +2994,6 @@ const message = {
|
||||
remote: 'Удаленный SSH',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: 'Настройки кэша',
|
||||
tabs: 'Кэш вкладок',
|
||||
pagination: 'Кэш пагинации',
|
||||
refresh: 'Кэш интервала обновления',
|
||||
search: 'Кэш поискового запроса',
|
||||
duration: 'Срок хранения кэша',
|
||||
'24h': '24 часа',
|
||||
week: 'Одна неделя',
|
||||
month: 'Один месяц',
|
||||
forever: 'Бессрочно',
|
||||
helper: 'Срок хранения отсчитывается с последнего изменения состояния. После его истечения восстанавливаются значения по умолчанию.',
|
||||
},
|
||||
|
||||
panel: 'Панель',
|
||||
user: 'Пользователь панели',
|
||||
userChange: 'Изменить пользователя панели',
|
||||
@@ -3741,6 +3744,7 @@ const message = {
|
||||
link: 'Ссылка',
|
||||
showCurrentArch: 'Приложения для текущей архитектуры сервера',
|
||||
syncLocalApp: 'Синхронизировать локальное приложение',
|
||||
localAppSyncToNodes: 'Включать дочерние узлы при синхронизации локальных приложений',
|
||||
memoryRequiredHelper: 'Текущее приложение требует {0} памяти',
|
||||
gpuConfig: 'Включить поддержку GPU',
|
||||
gpuConfigHelper:
|
||||
@@ -4364,10 +4368,15 @@ const message = {
|
||||
familyIncomplete: 'Цепочки {0} неполны',
|
||||
resetWithBackup: 'Сохранить правила и сбросить',
|
||||
resetOnly: 'Только сбросить',
|
||||
resetWithBackupHelper: 'Сохраните правила текущего модуля в JSON для восстановления при инициализации.',
|
||||
resetWithBackupHelper:
|
||||
'Сохраните правила текущего модуля в JSON для восстановления при инициализации или запуске.',
|
||||
resetOnlyHelper: 'Удалить текущие правила без создания новой копии. Существующие копии сохраняются.',
|
||||
backupDirectoryHelper:
|
||||
'Ниже перечислены действительные резервные копии правил из {0}. По умолчанию выбрана самая новая.',
|
||||
startOnly: 'Только запуск',
|
||||
startAndRestore: 'Запустить и восстановить из резервной копии',
|
||||
startWithBackupHelper:
|
||||
'Запустите брандмауэр как обычно или выберите существующую копию JSON для запуска и восстановления правил.',
|
||||
restoreAllRules: 'Инициализировать и восстановить правила',
|
||||
importRuleBackup: 'Импортировать из резервной копии',
|
||||
initializeOnly: 'Только инициализировать',
|
||||
|
||||
@@ -777,6 +777,15 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
'Tarayıcıda kullanılan IPv4 adresini veya ana bilgisayar adını protokol, bağlantı noktası veya yol olmadan girin. HTTPS, uygulamanın yerel sertifikasını kullanır.',
|
||||
deepSeekAuthHelper:
|
||||
'Kullanıcı adı: harf, rakam, nokta, alt çizgi veya tire. Parola: en az 12 karakter; başta/sonda boşluk veya satır sonu içeremez.',
|
||||
octopAuthHelper:
|
||||
'Parola: harf ve rakam içeren en az 8 karakter; yaygın parolalardan kaçının. Reddedilirse kurulum yönergeleri için uygulama günlüklerini kontrol edin.',
|
||||
initialCredentials: 'İlk giriş bilgileri; uygulamada değiştirildiyse güncel parolayı kullanın.',
|
||||
nativeAgentAccessUnavailable:
|
||||
'Mevcut kurulumda uyumlu HTTPS erişim adresi veya bağlantı noktası yok. Uygulama ayarlarını kontrol edin.',
|
||||
agent: 'Ajan',
|
||||
account: 'Model hesabı',
|
||||
batchOperation: 'Toplu işlem',
|
||||
@@ -1527,6 +1536,14 @@ const message = {
|
||||
extraHeaders: 'Ek Başlıklar',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Mevcut eşzamanlı istekler',
|
||||
sampleMoment: 'Örnekleme anında',
|
||||
sampleWindow: 'Örnekleme aralığı',
|
||||
requestUnit: 'istek',
|
||||
processing: 'İşleniyor',
|
||||
queued: 'Kuyrukta',
|
||||
requestTiming: 'İstek süreleri',
|
||||
completedRequests: 'Aralıkta tamamlanan istek sayısı',
|
||||
tokensPerSecond: 'tokens/sn',
|
||||
requestsPerSecond: 'İstek/sn',
|
||||
finishReasonLabels: {
|
||||
@@ -2995,20 +3012,6 @@ const message = {
|
||||
remote: 'Uzak SSH',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: 'Önbellek ayarları',
|
||||
tabs: 'Sekme önbelleği',
|
||||
pagination: 'Sayfalama önbelleği',
|
||||
refresh: 'Yenileme aralığı önbelleği',
|
||||
search: 'Arama alanı önbelleği',
|
||||
duration: 'Önbellek süresi',
|
||||
'24h': '24 saat',
|
||||
week: 'Bir hafta',
|
||||
month: 'Bir ay',
|
||||
forever: 'Süresiz',
|
||||
helper: 'Geçerlilik süresi son durum değişikliğinden itibaren hesaplanır. Süre dolduğunda varsayılan değerler geri yüklenir.',
|
||||
},
|
||||
|
||||
panel: 'Panel',
|
||||
user: 'Panel kullanıcısı',
|
||||
userChange: 'Panel kullanıcısını değiştir',
|
||||
@@ -3757,6 +3760,7 @@ const message = {
|
||||
link: 'Bağlantı',
|
||||
showCurrentArch: 'Mimari',
|
||||
syncLocalApp: 'Yerel Uygulamayı Senkronize Et',
|
||||
localAppSyncToNodes: 'Yerel uygulama eşitlemesine alt düğümleri dahil et',
|
||||
memoryRequiredHelper: 'Geçerli uygulama bellek gereksinimi {0}',
|
||||
gpuConfig: 'GPU Desteğini Etkinleştir',
|
||||
gpuConfigHelper:
|
||||
@@ -4382,10 +4386,15 @@ const message = {
|
||||
familyIncomplete: '{0} zincirleri eksik',
|
||||
resetWithBackup: 'Kuralları yedekle ve sıfırla',
|
||||
resetOnly: 'Yalnızca sıfırla',
|
||||
resetWithBackupHelper: 'Geçerli modül kurallarını başlatma sırasında geri yüklemek için JSON olarak kaydedin.',
|
||||
resetWithBackupHelper:
|
||||
'Geçerli modül kurallarını ilk kurulumda veya güvenlik duvarı başlatılırken geri yüklemek için JSON olarak kaydedin.',
|
||||
resetOnlyHelper: 'Yeni yedek oluşturmadan mevcut kuralları temizleyin. Önceki yedekler korunur.',
|
||||
backupDirectoryHelper:
|
||||
'{0} içindeki geçerli kural yedekleri listelenir. Varsayılan olarak en yeni dosya seçilir.',
|
||||
startOnly: 'Yalnızca başlat',
|
||||
startAndRestore: 'Başlat ve yedekten geri yükle',
|
||||
startWithBackupHelper:
|
||||
'Güvenlik duvarını normal başlatın veya başlatıp kuralları geri yüklemek için mevcut bir JSON yedeği seçin.',
|
||||
restoreAllRules: 'Başlat ve kuralları geri yükle',
|
||||
importRuleBackup: 'Yedekten içe aktar',
|
||||
initializeOnly: 'Yalnızca başlat',
|
||||
|
||||
@@ -739,6 +739,13 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
'填寫瀏覽器實際存取的 IPv4 位址或主機名稱,不含協定、連接埠或路徑。HTTPS 使用應用程式的本機憑證。',
|
||||
deepSeekAuthHelper: '使用者名稱僅支援字母、數字、點、底線及連字號;密碼至少 12 位,不含前後空格或換行。',
|
||||
octopAuthHelper:
|
||||
'密碼至少 8 位且包含字母及數字,避免常見弱密碼。若初始化拒絕該密碼,請查看應用程式日誌中的設定說明。',
|
||||
initialCredentials: '初始登入資訊;若已在應用程式內修改,以目前密碼為準。',
|
||||
nativeAgentAccessUnavailable: '現有安裝缺少相容的 HTTPS 存取位址或連接埠,請檢查應用程式設定。',
|
||||
agent: 'Agent',
|
||||
account: '模型帳號',
|
||||
batchOperation: '批次操作',
|
||||
@@ -1437,6 +1444,14 @@ const message = {
|
||||
extraHeaders: '額外請求標頭',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: '目前並行請求',
|
||||
sampleMoment: '取樣時刻',
|
||||
sampleWindow: '統計區間',
|
||||
requestUnit: '個請求',
|
||||
processing: '處理中',
|
||||
queued: '排隊中',
|
||||
requestTiming: '請求耗時',
|
||||
completedRequests: '區間完成請求數',
|
||||
tokensPerSecond: 'tokens/秒',
|
||||
requestsPerSecond: '請求/秒',
|
||||
finishReasonLabels: {
|
||||
@@ -2786,20 +2801,6 @@ const message = {
|
||||
stopSSHWarn: '是否斷開此SSH連接',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: '快取設定',
|
||||
tabs: '標籤頁快取',
|
||||
pagination: '分頁快取',
|
||||
refresh: '重新整理頻率快取',
|
||||
search: '搜尋輸入框快取',
|
||||
duration: '快取時間',
|
||||
'24h': '24h',
|
||||
week: '一週',
|
||||
month: '一個月',
|
||||
forever: '永久',
|
||||
helper: '有效期從狀態最後一次修改開始計算;過期後恢復預設值。',
|
||||
},
|
||||
|
||||
panel: '面板',
|
||||
user: '面板使用者',
|
||||
userChange: '修改面板使用者',
|
||||
@@ -3457,6 +3458,7 @@ const message = {
|
||||
link: '連結',
|
||||
showCurrentArch: '目前伺服器架構應用',
|
||||
syncLocalApp: '同步本機應用',
|
||||
localAppSyncToNodes: '同步本機應用時聯動子節點',
|
||||
memoryRequiredHelper: '目前應用記憶體需求 {0}',
|
||||
gpuConfig: '開啟 GPU 支援',
|
||||
gpuConfigHelper: '請確保機器有 NVIDIA GPU 並且安裝 NVIDIA 驅動 和 NVIDIA docker Container Toolkit',
|
||||
@@ -4024,9 +4026,12 @@ const message = {
|
||||
familyIncomplete: '{0} 鏈不完整',
|
||||
resetWithBackup: '備份規則後重置',
|
||||
resetOnly: '僅重置',
|
||||
resetWithBackupHelper: '將目前模組的規則儲存為 JSON,之後可在初始化時選擇備份還原。',
|
||||
resetWithBackupHelper: '將目前模組的規則儲存為 JSON,之後可在初始化或啟動時選擇備份還原。',
|
||||
resetOnlyHelper: '不建立新備份,直接清理目前規則;保留既有歷史備份。',
|
||||
backupDirectoryHelper: '自動讀取 {0} 下的有效規則備份,預設選擇最新檔案。',
|
||||
startOnly: '僅啟動',
|
||||
startAndRestore: '啟動並從備份還原',
|
||||
startWithBackupHelper: '可正常啟動防火牆,或選擇現有的 JSON 備份啟動並還原規則。',
|
||||
restoreAllRules: '初始化並還原規則',
|
||||
importRuleBackup: '從備份匯入',
|
||||
initializeOnly: '僅初始化',
|
||||
|
||||
@@ -756,6 +756,13 @@ const message = {
|
||||
},
|
||||
aiTools: {
|
||||
agents: {
|
||||
deepSeekAccessHostHelper:
|
||||
'填写浏览器实际访问的 IPv4 地址或主机名,不含协议、端口或路径。HTTPS 使用应用的本地证书。',
|
||||
deepSeekAuthHelper: '用户名仅支持字母、数字、点、下划线和连字符;密码至少 12 位,不含首尾空格或换行。',
|
||||
octopAuthHelper:
|
||||
'密码至少 8 位且包含字母和数字,避免常见弱密码。若初始化拒绝该密码,请查看应用日志中的设置说明。',
|
||||
initialCredentials: '初始登录信息;若已在应用内修改,以当前密码为准。',
|
||||
nativeAgentAccessUnavailable: '现有安装缺少兼容的 HTTPS 访问地址或端口,请检查应用设置。',
|
||||
agent: '智能体',
|
||||
account: '模型账号',
|
||||
batchOperation: '批量操作',
|
||||
@@ -1450,6 +1457,14 @@ const message = {
|
||||
extraHeaders: '额外请求头',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: '当前并发',
|
||||
sampleMoment: '采样时刻',
|
||||
sampleWindow: '统计区间',
|
||||
requestUnit: '个请求',
|
||||
processing: '处理中',
|
||||
queued: '排队中',
|
||||
requestTiming: '请求耗时',
|
||||
completedRequests: '区间完成请求数',
|
||||
tokensPerSecond: 'tokens/秒',
|
||||
requestsPerSecond: '请求/秒',
|
||||
finishReasonLabels: {
|
||||
@@ -2817,20 +2832,6 @@ const message = {
|
||||
stopSSHWarn: '是否断开此SSH连接',
|
||||
},
|
||||
setting: {
|
||||
panelCache: {
|
||||
title: '缓存设置',
|
||||
tabs: '标签页缓存',
|
||||
pagination: '翻页缓存',
|
||||
refresh: '刷新频率缓存',
|
||||
search: '搜索输入框缓存',
|
||||
duration: '缓存时间',
|
||||
'24h': '24h',
|
||||
week: '一周',
|
||||
month: '一月',
|
||||
forever: '永久',
|
||||
helper: '有效期从状态最后一次修改开始计算;过期后恢复默认值。',
|
||||
},
|
||||
|
||||
panel: '面板',
|
||||
user: '面板用户',
|
||||
userChange: '修改面板用户',
|
||||
@@ -3498,6 +3499,7 @@ const message = {
|
||||
link: '链接',
|
||||
showCurrentArch: '本服务器架构应用',
|
||||
syncLocalApp: '同步本地应用',
|
||||
localAppSyncToNodes: '同步本地应用时联动子节点',
|
||||
memoryRequiredHelper: '当前应用内存需求 {0}',
|
||||
gpuConfig: '开启 GPU 支持',
|
||||
gpuConfigHelper: '请确保机器有 NVIDIA GPU 并且安装 NVIDIA 驱动 和 NVIDIA docker Container Toolkit',
|
||||
@@ -4069,9 +4071,12 @@ const message = {
|
||||
familyIncomplete: '{0} 链不完整',
|
||||
resetWithBackup: '备份规则后重置',
|
||||
resetOnly: '仅重置',
|
||||
resetWithBackupHelper: '将当前模块的规则保存为 JSON,后续可在初始化时选择备份恢复。',
|
||||
resetWithBackupHelper: '将当前模块的规则保存为 JSON,后续可在初始化或启动时选择备份恢复。',
|
||||
resetOnlyHelper: '不生成新备份,直接清理当前规则;已有历史备份保留。',
|
||||
backupDirectoryHelper: '自动读取 {0} 下的有效规则备份,默认选择最新文件。',
|
||||
startOnly: '仅启动',
|
||||
startAndRestore: '启动并从备份恢复',
|
||||
startWithBackupHelper: '可正常启动防火墙,或选择已有的 JSON 备份启动并恢复规则。',
|
||||
restoreAllRules: '初始化并恢复规则',
|
||||
importRuleBackup: '从备份导入',
|
||||
initializeOnly: '仅初始化',
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { applyPanelCacheSettings } from '@/utils/panel-cache';
|
||||
import { ref, computed, onBeforeUnmount, onMounted, watch } from 'vue';
|
||||
import { RouteRecordRaw, useRoute, useRouter } from 'vue-router';
|
||||
import { loadingSvg } from '@/utils/svg';
|
||||
@@ -142,7 +141,6 @@ const search = async () => {
|
||||
settingInfo = res.data;
|
||||
version.value = res.data.systemVersion;
|
||||
menuAccordion.value = res.data.menuAccordion === 'Enable';
|
||||
applyPanelCacheSettings(res.data.panelCache);
|
||||
} catch (error) {
|
||||
version.value = '';
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { prunePanelCache } from '@/utils/panel-cache';
|
||||
import { onMounted, computed, ref, watch, onBeforeUnmount } from 'vue';
|
||||
import { Sidebar, Footer, AppMain, MobileHeader, Tabs } from './components';
|
||||
import TerminalHost from '@/components/terminal/host.vue';
|
||||
@@ -107,7 +106,6 @@ const loadingText = computed(() =>
|
||||
);
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
let cacheCleanupTimer: ReturnType<typeof setInterval> | undefined;
|
||||
|
||||
const classObj = computed(() => {
|
||||
return {
|
||||
@@ -188,12 +186,10 @@ const loadStatus = async () => {
|
||||
}
|
||||
};
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(cacheCleanupTimer);
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
});
|
||||
onMounted(() => {
|
||||
cacheCleanupTimer = setInterval(() => prunePanelCache(), 300000);
|
||||
if (openMenuTabs.value && !tabsStore.activeTabPath) {
|
||||
handleMenuClick('/');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { panelCacheKey, readPanelCache, writePanelCache, removePanelCache, prunePanelCache } from '@/utils/panel-cache';
|
||||
import router from '@/routers/router';
|
||||
import NProgress from '@/config/nprogress';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
@@ -103,34 +102,29 @@ router.beforeEach(async (to, from) => {
|
||||
if (to.name === 'Expired') {
|
||||
return true;
|
||||
}
|
||||
const activeMenuKey = panelCacheKey(String(to.meta.activeMenu || ''));
|
||||
const activeMenuKey = 'cachedRoute' + (to.meta.activeMenu || '');
|
||||
if (to.query.uncached != undefined) {
|
||||
const query = { ...to.query };
|
||||
delete query.uncached;
|
||||
removePanelCache('tabs', activeMenuKey);
|
||||
localStorage.removeItem(activeMenuKey);
|
||||
return { path: to.path, query };
|
||||
}
|
||||
|
||||
const cachedRoute = readPanelCache<string>('tabs', activeMenuKey);
|
||||
const cachedRoute = localStorage.getItem(activeMenuKey);
|
||||
if (
|
||||
to.meta.activeMenu &&
|
||||
to.meta.activeMenu != from.meta.activeMenu &&
|
||||
typeof cachedRoute === 'string' &&
|
||||
cachedRoute &&
|
||||
cachedRoute !== to.path &&
|
||||
!isRedirecting
|
||||
) {
|
||||
const cachedRouteInfo = router.resolve(cachedRoute);
|
||||
if (
|
||||
cachedRouteInfo.matched.length > 0 &&
|
||||
cachedRouteInfo.meta.activeMenu === to.meta.activeMenu &&
|
||||
hasRouteAccess(cachedRouteInfo)
|
||||
) {
|
||||
if (cachedRouteInfo.matched.length > 0 && hasRouteAccess(cachedRouteInfo)) {
|
||||
isRedirecting = true;
|
||||
NProgress.done();
|
||||
return cachedRoute;
|
||||
}
|
||||
removePanelCache('tabs', activeMenuKey);
|
||||
localStorage.removeItem(activeMenuKey);
|
||||
}
|
||||
|
||||
if (!hasRouteAccess(to)) {
|
||||
@@ -141,13 +135,7 @@ router.beforeEach(async (to, from) => {
|
||||
return true;
|
||||
});
|
||||
|
||||
router.afterEach((to, _from, failure) => {
|
||||
prunePanelCache();
|
||||
if (failure) {
|
||||
isRedirecting = false;
|
||||
NProgress.done();
|
||||
return;
|
||||
}
|
||||
router.afterEach((to) => {
|
||||
if (to.meta.activeMenu && !to.meta.ignoreTab && !isRedirecting) {
|
||||
let notMathParam = true;
|
||||
if (to.matched.some((record) => record.path.includes(':'))) {
|
||||
@@ -155,13 +143,13 @@ router.afterEach((to, _from, failure) => {
|
||||
}
|
||||
if (notMathParam) {
|
||||
if (to.meta.activeMenu === '/cronjobs' && to.path === '/cronjobs/cronjob/operate') {
|
||||
writePanelCache('tabs', panelCacheKey(String(to.meta.activeMenu)), '/cronjobs/cronjob');
|
||||
localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/cronjobs/cronjob');
|
||||
} else if (to.meta.activeMenu === '/containers' && to.path === '/containers/container/operate') {
|
||||
writePanelCache('tabs', panelCacheKey(String(to.meta.activeMenu)), '/containers/container');
|
||||
localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/containers/container');
|
||||
} else if (to.meta.activeMenu === '/toolbox' && to.path === '/toolbox/clam/setting') {
|
||||
writePanelCache('tabs', panelCacheKey(String(to.meta.activeMenu)), '/toolbox/clam');
|
||||
localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/toolbox/clam');
|
||||
} else {
|
||||
writePanelCache('tabs', panelCacheKey(String(to.meta.activeMenu)), to.path);
|
||||
localStorage.setItem('cachedRoute' + to.meta.activeMenu, to.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { DeviceType } from '@/enums/app';
|
||||
import type { PanelCacheSettings } from '@/utils/panel-cache';
|
||||
export interface ThemeConfigProp {
|
||||
panelName: string;
|
||||
primary: string;
|
||||
@@ -35,8 +34,6 @@ export interface GlobalState {
|
||||
isFullScreen: boolean;
|
||||
openMenuTabs: boolean;
|
||||
menuAccordion: boolean;
|
||||
panelCache: PanelCacheSettings;
|
||||
cacheUserID: string;
|
||||
watermark: Watermark | null;
|
||||
watermarkShow: boolean;
|
||||
isLoading: boolean;
|
||||
|
||||
@@ -6,7 +6,6 @@ import { DeviceType } from '@/enums/app';
|
||||
import i18n, { setActiveLocale } from '@/lang';
|
||||
import { isMasterOnlyPermissionCode, setMasterOnlyPermissionCodes, toManageCode } from '@/utils/permission-codes';
|
||||
import { clearPageStateCache } from '@/utils/page-state-cache';
|
||||
import { defaultPanelCache } from '@/utils/panel-cache';
|
||||
|
||||
const CN_DOCS_URL = 'https://docs.fit2cloud.com/1panel';
|
||||
const INTL_DOCS_URL = 'https://1panel.pro/docs/v2';
|
||||
@@ -34,8 +33,6 @@ const GlobalStore = defineStore('GlobalState', {
|
||||
isFullScreen: false,
|
||||
openMenuTabs: false,
|
||||
menuAccordion: false,
|
||||
panelCache: { ...defaultPanelCache },
|
||||
cacheUserID: '',
|
||||
watermark: null,
|
||||
watermarkShow: false,
|
||||
isLoading: false,
|
||||
@@ -126,7 +123,6 @@ const GlobalStore = defineStore('GlobalState', {
|
||||
},
|
||||
clearAuthInfo() {
|
||||
clearPageStateCache();
|
||||
this.cacheUserID = '';
|
||||
this.permissions = [];
|
||||
this.masterOnlyPermissions = [];
|
||||
this.nodeRoles = [];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { bindSearchState } from '@/utils/search-reset';
|
||||
import { panelCacheKey } from '@/utils/panel-cache';
|
||||
import { bindSearchReset } from '@/utils/search-reset';
|
||||
import router from '@/routers/router';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, reactive, toRef } from 'vue';
|
||||
import { MsgError } from '@/utils/message';
|
||||
@@ -48,7 +49,8 @@ export const ProcessStore = defineStore('ProcessStore', () => {
|
||||
port: undefined,
|
||||
});
|
||||
|
||||
const searchState = bindSearchState(() => panelCacheKey('host/process'), {
|
||||
const { currentNode } = useGlobalStore();
|
||||
bindSearchReset([() => router.currentRoute.value.path, currentNode], {
|
||||
pid: toRef(psSearch, 'pid'),
|
||||
name: toRef(psSearch, 'name'),
|
||||
username: toRef(psSearch, 'username'),
|
||||
@@ -319,7 +321,6 @@ export const ProcessStore = defineStore('ProcessStore', () => {
|
||||
};
|
||||
|
||||
return {
|
||||
restoreSearchState: searchState.restore,
|
||||
isConnected,
|
||||
isConnecting,
|
||||
psData,
|
||||
|
||||
@@ -1,10 +1,52 @@
|
||||
import i18n from '@/lang';
|
||||
import { compareVersion } from '@/utils/version';
|
||||
import type { AI } from '@/api/interface/ai';
|
||||
|
||||
const openclawHTTPSVersion = '2026.3.13';
|
||||
const openclawHTTPVersion = '2026.3.23';
|
||||
const openclawDefaultAccessHost = '127.0.0.1';
|
||||
|
||||
export const isAgentType = (value: unknown): value is AI.AgentType =>
|
||||
typeof value === 'string' && ['openclaw', 'copaw', 'hermes-agent', 'deepseek-harness', 'octop'].includes(value);
|
||||
|
||||
export const isDeepSeekAccessHost = (value: string): boolean => {
|
||||
if (/^[0-9.]+$/.test(value)) {
|
||||
return (
|
||||
value.split('.').length === 4 &&
|
||||
value.split('.').every((part) => /^(0|[1-9]\d{0,2})$/.test(part) && +part <= 255)
|
||||
);
|
||||
}
|
||||
return (
|
||||
value.length > 0 &&
|
||||
value.length <= 253 &&
|
||||
value === value.trim() &&
|
||||
value.split('.').every((part) => /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/.test(part))
|
||||
);
|
||||
};
|
||||
|
||||
export const isOctopPassword = (value: string): boolean =>
|
||||
[...value].length >= 8 &&
|
||||
value === value.trim() &&
|
||||
!/[\r\n\0]/.test(value) &&
|
||||
/\p{L}/u.test(value) &&
|
||||
/\p{Nd}/u.test(value) &&
|
||||
![
|
||||
'password',
|
||||
'password1',
|
||||
'password12',
|
||||
'password123',
|
||||
'12345678',
|
||||
'123456789',
|
||||
'qwerty123',
|
||||
'admin123',
|
||||
'welcome1',
|
||||
'letmein1',
|
||||
'changeme1',
|
||||
'octop123',
|
||||
'abc12345',
|
||||
'iloveyou1',
|
||||
].includes(value.toLowerCase());
|
||||
|
||||
export const getAgentProviderDisplayName = (provider: string, displayName?: string): string => {
|
||||
if (provider === 'custom' || displayName === 'Custom') {
|
||||
return i18n.global.t('container.custom');
|
||||
@@ -27,7 +69,7 @@ export const supportsAgentToken = (agentType: string): boolean => {
|
||||
};
|
||||
|
||||
export const supportsAgentDashboardAuth = (agentType: string): boolean => {
|
||||
return agentType === 'hermes-agent' || agentType === 'copaw';
|
||||
return ['hermes-agent', 'copaw', 'deepseek-harness', 'octop'].includes(agentType);
|
||||
};
|
||||
|
||||
export const isOpenclawHTTPSWindowVersion = (version: string): boolean => {
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import GlobalStore from '@/store/modules/global';
|
||||
|
||||
export type CacheCategory = 'tabs' | 'pagination' | 'refresh' | 'search';
|
||||
export interface PanelCacheSettings {
|
||||
tabs: boolean;
|
||||
pagination: boolean;
|
||||
refresh: boolean;
|
||||
search: boolean;
|
||||
duration: '24h' | 'week' | 'month' | 'forever';
|
||||
}
|
||||
export const defaultPanelCache: PanelCacheSettings = {
|
||||
tabs: true,
|
||||
pagination: true,
|
||||
refresh: true,
|
||||
search: false,
|
||||
duration: '24h',
|
||||
};
|
||||
const categories: CacheCategory[] = ['tabs', 'pagination', 'refresh', 'search'];
|
||||
const durations = { '24h': 86400000, week: 604800000, month: 2592000000, forever: Infinity };
|
||||
const prefix = '1panel:page-cache:v1:';
|
||||
interface CacheEntry {
|
||||
category: CacheCategory;
|
||||
updatedAt: number;
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
export const applyPanelCacheSettings = (raw: string) => {
|
||||
let value: Partial<PanelCacheSettings> = {};
|
||||
try {
|
||||
value = JSON.parse(raw) || {};
|
||||
} catch {
|
||||
// Older servers do not return this setting yet.
|
||||
}
|
||||
const settings = { ...defaultPanelCache };
|
||||
for (const category of categories) {
|
||||
if (typeof value[category] === 'boolean') settings[category] = value[category];
|
||||
}
|
||||
if (Object.hasOwn(durations, value.duration || '')) settings.duration = value.duration;
|
||||
const store = GlobalStore();
|
||||
const changed =
|
||||
settings.duration !== store.panelCache.duration ||
|
||||
categories.some((category) => settings[category] !== store.panelCache[category]);
|
||||
if (changed) store.panelCache = settings;
|
||||
// Repeated settings loads share the normal cleanup interval.
|
||||
prunePanelCache(changed);
|
||||
};
|
||||
|
||||
export const panelCacheKey = (key: string): string => {
|
||||
const { cacheUserID, currentNode } = GlobalStore();
|
||||
return JSON.stringify([cacheUserID, currentNode, key]);
|
||||
};
|
||||
const storageKey = (category: CacheCategory, key: string) => prefix + JSON.stringify([category, key]);
|
||||
const validEntry = (entry: CacheEntry, settings: PanelCacheSettings, now: number) => {
|
||||
return (
|
||||
entry &&
|
||||
categories.includes(entry.category) &&
|
||||
settings[entry.category] &&
|
||||
Number.isFinite(entry.updatedAt) &&
|
||||
entry.updatedAt <= now &&
|
||||
now - entry.updatedAt < durations[settings.duration]
|
||||
);
|
||||
};
|
||||
|
||||
export const readPanelCache = <T>(category: CacheCategory, key: string): T | undefined => {
|
||||
const store = GlobalStore();
|
||||
if (!store.cacheUserID || !store.panelCache[category]) return;
|
||||
try {
|
||||
const target = storageKey(category, key);
|
||||
const raw = localStorage.getItem(target);
|
||||
if (!raw) return;
|
||||
const entry: CacheEntry = JSON.parse(raw);
|
||||
if (entry.category === category && validEntry(entry, store.panelCache, Date.now())) return entry.value as T;
|
||||
localStorage.removeItem(target);
|
||||
} catch {
|
||||
// Storage can be unavailable or contain invalid data; use page defaults.
|
||||
}
|
||||
};
|
||||
|
||||
export const writePanelCache = (category: CacheCategory, key: string, value: unknown) => {
|
||||
const store = GlobalStore();
|
||||
if (!store.cacheUserID || !store.panelCache[category]) return;
|
||||
try {
|
||||
// Repeated visits and unchanged values must not extend the expiration time.
|
||||
const previous = readPanelCache(category, key);
|
||||
if (previous !== undefined && JSON.stringify(previous) === JSON.stringify(value)) return;
|
||||
localStorage.setItem(storageKey(category, key), JSON.stringify({ category, updatedAt: Date.now(), value }));
|
||||
} catch {
|
||||
// A full or disabled localStorage must not block page interactions.
|
||||
}
|
||||
};
|
||||
|
||||
export const removePanelCache = (category: CacheCategory, key: string) => {
|
||||
try {
|
||||
localStorage.removeItem(storageKey(category, key));
|
||||
} catch {
|
||||
/* Storage is optional. */
|
||||
}
|
||||
};
|
||||
|
||||
let lastPruned = 0;
|
||||
export const prunePanelCache = (force = false) => {
|
||||
const now = Date.now();
|
||||
if (!force && now - lastPruned < 300000) return;
|
||||
lastPruned = now;
|
||||
const settings = GlobalStore().panelCache;
|
||||
try {
|
||||
for (let index = localStorage.length - 1; index >= 0; index--) {
|
||||
const key = localStorage.key(index);
|
||||
if (!key) continue;
|
||||
if (key.startsWith('1panel:search-state:v2:') || key.startsWith('cachedRoute')) {
|
||||
// Legacy entries have neither ownership nor timestamps and cannot be safely restored.
|
||||
localStorage.removeItem(key);
|
||||
} else if (key.startsWith(prefix)) {
|
||||
try {
|
||||
if (validEntry(JSON.parse(localStorage.getItem(key)), settings, now)) continue;
|
||||
} catch {
|
||||
/* Remove malformed entries too. */
|
||||
}
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* Storage is optional. */
|
||||
}
|
||||
};
|
||||
|
||||
export const readCachedPageSize = (key: string): number | undefined => {
|
||||
const value = readPanelCache<number>('pagination', panelCacheKey(`size:${key}`));
|
||||
return Number.isInteger(value) && value > 0 ? value : undefined;
|
||||
};
|
||||
@@ -22,7 +22,6 @@ export const syncAuthInfo = async (currentNode?: string) => {
|
||||
const storeCurrentNode = globalStore.currentNode;
|
||||
if (!globalStore.isEnterprise) {
|
||||
const res = await getUserInfo();
|
||||
globalStore.cacheUserID = `community:${res.data.id}`;
|
||||
globalStore.setAuthInfo({
|
||||
isAdmin: res.data.role === 'ADMIN',
|
||||
permissions: res.data.permissions || [],
|
||||
@@ -32,7 +31,6 @@ export const syncAuthInfo = async (currentNode?: string) => {
|
||||
return res.data;
|
||||
}
|
||||
const res = await getEnterpriseUserInfo(currentNode ?? storeCurrentNode);
|
||||
globalStore.cacheUserID = `enterprise:${res.data.id}`;
|
||||
globalStore.setAuthInfo({
|
||||
isAdmin: res.data.role === 'ADMIN',
|
||||
permissions: res.data.permissions || [],
|
||||
|
||||
@@ -1,165 +1,12 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { nextTick, onActivated, onDeactivated, onScopeDispose, watch, type Ref } from 'vue';
|
||||
import { panelCacheKey, readPanelCache, writePanelCache } from '@/utils/panel-cache';
|
||||
import GlobalStore from '@/store/modules/global';
|
||||
import { watch, type Ref, type WatchSource } from 'vue';
|
||||
|
||||
export type SearchFields = Record<string, Ref<string | number | undefined>>;
|
||||
type SearchValues = Record<string, string | number | undefined>;
|
||||
|
||||
// Keep categories separate so changing search text does not renew pagination's lifetime.
|
||||
const searchStorage = {
|
||||
save(key: string, values: SearchValues) {
|
||||
for (const category of ['search', 'pagination'] as const) {
|
||||
const entries = Object.entries(values).filter(
|
||||
([name]) => (name === 'page' || name === 'pageSize') === (category === 'pagination'),
|
||||
);
|
||||
if (entries.length) {
|
||||
writePanelCache(
|
||||
category,
|
||||
key,
|
||||
Object.fromEntries(entries.map(([name, value]) => [name, value ?? null])),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
read(key: string): SearchValues {
|
||||
const values = {
|
||||
...readPanelCache<SearchValues>('search', key),
|
||||
...readPanelCache<SearchValues>('pagination', key),
|
||||
};
|
||||
return Object.fromEntries(
|
||||
Object.entries(values)
|
||||
.filter(
|
||||
([, value]) =>
|
||||
value === null ||
|
||||
typeof value === 'string' ||
|
||||
(typeof value === 'number' && Number.isFinite(value)),
|
||||
)
|
||||
.map(([name, value]) => [name, value === null ? undefined : value]),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const bindSearchState = (key: () => string | undefined, fields: SearchFields, storage = searchStorage) => {
|
||||
const defaults = Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value]));
|
||||
const names = Object.keys(fields).sort((left, right) => Number(left === 'page') - Number(right === 'page'));
|
||||
let pending: { key: string; values: SearchValues } | undefined;
|
||||
let queued = false;
|
||||
const flush = () => {
|
||||
if (!pending) return;
|
||||
const snapshot = pending;
|
||||
pending = undefined;
|
||||
storage.save(snapshot.key, snapshot.values);
|
||||
export const bindSearchReset = (sources: WatchSource[], fields: SearchFields) => {
|
||||
const defaults = Object.values(fields).map((field) => ({ field, value: field.value }));
|
||||
const reset = () => {
|
||||
for (const { field, value } of defaults) field.value = value;
|
||||
};
|
||||
let restoring = false;
|
||||
let activeKey: string | undefined;
|
||||
let active = true;
|
||||
let resuming = false;
|
||||
let resumeVersion = 0;
|
||||
const pause = () => {
|
||||
flush();
|
||||
active = false;
|
||||
resuming = false;
|
||||
resumeVersion++;
|
||||
};
|
||||
onScopeDispose(pause);
|
||||
const restore = () => {
|
||||
// Commit the previous node/resource before restoring another one.
|
||||
flush();
|
||||
activeKey = key();
|
||||
if (!activeKey) return;
|
||||
const saved = storage.read(activeKey) || defaults;
|
||||
restoring = true;
|
||||
try {
|
||||
// Restore pagination last: search/filter watchers may reset it to page one.
|
||||
for (const name of names) {
|
||||
const value = Object.hasOwn(saved, name) ? saved[name] : defaults[name];
|
||||
const initial = defaults[name];
|
||||
const invalidPage =
|
||||
(name === 'page' || name === 'pageSize') &&
|
||||
(typeof value !== 'number' || !Number.isInteger(value) || value < 1);
|
||||
fields[name].value =
|
||||
invalidPage || (initial !== undefined && value !== undefined && typeof value !== typeof initial)
|
||||
? initial
|
||||
: value;
|
||||
}
|
||||
} finally {
|
||||
restoring = false;
|
||||
}
|
||||
};
|
||||
watch(
|
||||
key,
|
||||
() => {
|
||||
if (active || resuming) restore();
|
||||
},
|
||||
{ immediate: true, flush: 'sync' },
|
||||
);
|
||||
watch(
|
||||
() => Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value])),
|
||||
(values, previous) => {
|
||||
if (restoring || !active || !activeKey) return;
|
||||
const changed = Object.entries(values).filter(([name, value]) => value !== previous[name]);
|
||||
const paginationChanged = changed.some(([name]) => name === 'page' || name === 'pageSize');
|
||||
const searchChanged = changed.some(([name]) => name !== 'page' && name !== 'pageSize');
|
||||
const snapshot = Object.fromEntries(
|
||||
Object.entries(values).filter(([name]) =>
|
||||
name === 'page' || name === 'pageSize' ? paginationChanged : searchChanged,
|
||||
),
|
||||
);
|
||||
pending = { key: activeKey, values: { ...pending?.values, ...snapshot } };
|
||||
if (queued) return;
|
||||
queued = true;
|
||||
queueMicrotask(() => {
|
||||
queued = false;
|
||||
flush();
|
||||
});
|
||||
},
|
||||
{ flush: 'sync' },
|
||||
);
|
||||
return {
|
||||
restore,
|
||||
pause,
|
||||
resume: async () => {
|
||||
const version = ++resumeVersion;
|
||||
resuming = true;
|
||||
active = false;
|
||||
restore();
|
||||
const restoredKey = activeKey;
|
||||
const page = fields.page?.value;
|
||||
await nextTick();
|
||||
if (version !== resumeVersion) return false;
|
||||
// Search watchers may reset the page while restoring a changed query.
|
||||
if (activeKey === restoredKey && fields.page && page !== undefined) fields.page.value = page;
|
||||
active = true;
|
||||
resuming = false;
|
||||
return true;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface SearchStateOptions {
|
||||
scope?: () => string | number | undefined;
|
||||
onRevisit: () => unknown;
|
||||
}
|
||||
|
||||
/** Keep queries across page visits and reload data when a cached page becomes active. */
|
||||
export const useSearchState = (page: string, fields: SearchFields, options: SearchStateOptions) => {
|
||||
const { cacheUserID } = storeToRefs(GlobalStore());
|
||||
const binding = bindSearchState(() => {
|
||||
const scope = options.scope?.();
|
||||
if (options.scope && scope === undefined) return undefined;
|
||||
// Track account and node changes, including initial auth synchronisation.
|
||||
if (!cacheUserID.value) return undefined;
|
||||
return panelCacheKey(JSON.stringify([page, scope ?? '']));
|
||||
}, fields);
|
||||
let deactivated = false;
|
||||
onDeactivated(() => {
|
||||
deactivated = true;
|
||||
binding.pause();
|
||||
});
|
||||
onActivated(async () => {
|
||||
if (!deactivated) return;
|
||||
deactivated = false;
|
||||
if (await binding.resume()) return options.onRevisit();
|
||||
});
|
||||
watch(sources, reset, { flush: 'sync' });
|
||||
return reset;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { applyPanelCacheSettings } from '@/utils/panel-cache';
|
||||
import {
|
||||
getLicenseStatus,
|
||||
getMasterLicenseStatus,
|
||||
@@ -87,7 +86,6 @@ const loadDataFromDB = async () => {
|
||||
globalStore.entrance = res.data.securityEntrance;
|
||||
globalStore.openMenuTabs = res.data.menuTabs === 'Enable';
|
||||
globalStore.menuAccordion = res.data.menuAccordion === 'Enable';
|
||||
applyPanelCacheSettings(res.data.panelCache);
|
||||
};
|
||||
|
||||
export async function loadProductProFromDB() {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
<el-option label="OpenClaw" value="openclaw" />
|
||||
<el-option label="Hermes Agent" value="hermes-agent" />
|
||||
<el-option label="QwenPaw" value="copaw" />
|
||||
<el-option label="DeepSeek Harness" value="deepseek-harness" />
|
||||
<el-option label="Octop" value="octop" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
@@ -19,10 +21,19 @@
|
||||
<el-select v-model="form.appVersion" filterable>
|
||||
<el-option v-for="item in versions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<span v-if="versions.length === 0" class="input-help">{{ $t('commons.msg.noneData') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('aiTools.agents.webuiPort')" prop="webUIPort">
|
||||
<el-input-number v-model="form.webUIPort" :min="1" :max="65535" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.agentType === 'deepseek-harness'"
|
||||
:label="$t('setting.systemIP')"
|
||||
prop="accessHost"
|
||||
>
|
||||
<el-input v-model="form.accessHost" />
|
||||
<span class="input-help">{{ $t('aiTools.agents.deepSeekAccessHostHelper') }}</span>
|
||||
</el-form-item>
|
||||
<template v-if="supportsAgentDashboardAuth(form.agentType)">
|
||||
<el-form-item :label="$t('commons.login.username')" prop="dashboardUsername">
|
||||
<el-input v-model="form.dashboardUsername" />
|
||||
@@ -35,6 +46,12 @@
|
||||
{{ $t('commons.button.random') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<span class="input-help" v-if="form.agentType === 'deepseek-harness'">
|
||||
{{ $t('aiTools.agents.deepSeekAuthHelper') }}
|
||||
</span>
|
||||
<span class="input-help" v-if="form.agentType === 'octop'">
|
||||
{{ $t('aiTools.agents.octopAuthHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item
|
||||
@@ -130,6 +147,9 @@ import { getRandomStr, newUUID } from '@/utils/id';
|
||||
import {
|
||||
buildDefaultAllowedOrigin,
|
||||
getAgentProviderDisplayName,
|
||||
isAgentType,
|
||||
isDeepSeekAccessHost,
|
||||
isOctopPassword,
|
||||
parseAllowedOriginsInput,
|
||||
supportsAgentDashboardAuth,
|
||||
validateAllowedOriginsInput,
|
||||
@@ -138,6 +158,7 @@ import { App } from '@/api/interface/app';
|
||||
import AdvancedSetting from '@/components/advanced-setting/index.vue';
|
||||
import AccountAddDialog from '@/views/ai/agents/model/add/index.vue';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const emit = defineEmits(['search', 'task']);
|
||||
|
||||
@@ -161,6 +182,7 @@ const form = reactive({
|
||||
agentType: 'openclaw' as AI.AgentType,
|
||||
appVersion: '',
|
||||
webUIPort: 18789,
|
||||
accessHost: '',
|
||||
allowedOrigins: '',
|
||||
provider: '',
|
||||
accountId: undefined as unknown as number,
|
||||
@@ -185,7 +207,10 @@ const form = reactive({
|
||||
const showModelConfig = computed(() => form.agentType === 'openclaw' || form.agentType === 'hermes-agent');
|
||||
|
||||
const generateDashboardPassword = () => {
|
||||
form.dashboardPassword = getRandomStr(8);
|
||||
form.dashboardPassword =
|
||||
form.agentType === 'deepseek-harness' || form.agentType === 'octop'
|
||||
? `Aa2${Array.from(crypto.getRandomValues(new Uint8Array(16)), (byte) => byte.toString(16).padStart(2, '0')).join('')}`
|
||||
: getRandomStr(8);
|
||||
};
|
||||
|
||||
const ensureDashboardAuth = () => {
|
||||
@@ -201,7 +226,7 @@ const ensureDashboardAuth = () => {
|
||||
};
|
||||
|
||||
const setDefaultWebUIPort = () => {
|
||||
if (form.agentType === 'copaw') {
|
||||
if (form.agentType === 'copaw' || form.agentType === 'octop') {
|
||||
form.webUIPort = 8088;
|
||||
return;
|
||||
}
|
||||
@@ -209,6 +234,10 @@ const setDefaultWebUIPort = () => {
|
||||
form.webUIPort = 9119;
|
||||
return;
|
||||
}
|
||||
if (form.agentType === 'deepseek-harness') {
|
||||
form.webUIPort = 10443;
|
||||
return;
|
||||
}
|
||||
form.webUIPort = 18789;
|
||||
};
|
||||
|
||||
@@ -217,6 +246,19 @@ const rules = reactive({
|
||||
agentType: [Rules.requiredSelect],
|
||||
appVersion: [Rules.requiredSelect],
|
||||
webUIPort: [Rules.requiredInput],
|
||||
accessHost: [
|
||||
Rules.requiredInput,
|
||||
{
|
||||
validator: (_rule: any, value: string, callback: (error?: Error) => void) => {
|
||||
callback(
|
||||
isDeepSeekAccessHost(value)
|
||||
? undefined
|
||||
: new Error(i18n.global.t('aiTools.agents.deepSeekAccessHostHelper')),
|
||||
);
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
allowedOrigins: [
|
||||
Rules.requiredInput,
|
||||
{
|
||||
@@ -234,8 +276,35 @@ const rules = reactive({
|
||||
provider: [Rules.requiredSelect],
|
||||
accountId: [Rules.requiredSelect],
|
||||
model: [Rules.requiredInput],
|
||||
dashboardUsername: [Rules.requiredInput],
|
||||
dashboardPassword: [Rules.requiredInput],
|
||||
dashboardUsername: [
|
||||
Rules.requiredInput,
|
||||
{
|
||||
validator: (_rule: any, value: string, callback: (error?: Error) => void) => {
|
||||
callback(
|
||||
form.agentType === 'deepseek-harness' && !/^[A-Za-z0-9._-]+$/.test(value)
|
||||
? new Error(i18n.global.t('aiTools.agents.deepSeekAuthHelper'))
|
||||
: undefined,
|
||||
);
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
dashboardPassword: [
|
||||
Rules.requiredInput,
|
||||
{
|
||||
validator: (_rule: any, value: string, callback: (error?: Error) => void) => {
|
||||
const invalidCharacters = value !== value.trim() || /[\r\n\0]/.test(value);
|
||||
if (form.agentType === 'deepseek-harness' && (invalidCharacters || [...value].length < 12)) {
|
||||
callback(new Error(i18n.global.t('aiTools.agents.deepSeekAuthHelper')));
|
||||
} else if (form.agentType === 'octop' && !isOctopPassword(value)) {
|
||||
callback(new Error(i18n.global.t('aiTools.agents.octopAuthHelper')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
containerName: [Rules.containerName],
|
||||
restartPolicy: [Rules.requiredSelect],
|
||||
cpuQuota: [checkNumberRange(0, 99999)],
|
||||
@@ -273,7 +342,13 @@ const loadSystemIP = async () => {
|
||||
};
|
||||
|
||||
const loadVersions = async (appKey: AI.AgentType) => {
|
||||
versions.value = [];
|
||||
appInfo.value = undefined;
|
||||
form.dockerCompose = '';
|
||||
const res = await getAppByKey(appKey);
|
||||
if (form.agentType !== appKey) {
|
||||
return;
|
||||
}
|
||||
appInfo.value = res.data;
|
||||
versions.value = res.data.versions || [];
|
||||
if (!form.appVersion && versions.value.length > 0) {
|
||||
@@ -287,6 +362,10 @@ const getDefaultAgentName = (agentType: AI.AgentType) => {
|
||||
return 'QwenPaw';
|
||||
case 'hermes-agent':
|
||||
return 'Hermes-Agent';
|
||||
case 'deepseek-harness':
|
||||
return 'DeepSeek-Harness';
|
||||
case 'octop':
|
||||
return 'Octop';
|
||||
default:
|
||||
return 'OpenClaw';
|
||||
}
|
||||
@@ -402,7 +481,9 @@ const handleAgentTypeChange = async () => {
|
||||
form.name === 'OpenClaw' ||
|
||||
form.name === 'CoPaw' ||
|
||||
form.name === 'QwenPaw' ||
|
||||
form.name === 'Hermes-Agent'
|
||||
form.name === 'Hermes-Agent' ||
|
||||
form.name === 'DeepSeek-Harness' ||
|
||||
form.name === 'Octop'
|
||||
) {
|
||||
form.name = getDefaultAgentName(form.agentType);
|
||||
}
|
||||
@@ -413,6 +494,10 @@ const handleAgentTypeChange = async () => {
|
||||
form.accountId = undefined as unknown as number;
|
||||
form.baseURL = '';
|
||||
ensureDashboardAuth();
|
||||
if (form.agentType === 'deepseek-harness' && !form.accessHost) {
|
||||
await loadSystemIP();
|
||||
form.accessHost = isDeepSeekAccessHost(systemIP.value) ? systemIP.value : '';
|
||||
}
|
||||
if (form.agentType === 'openclaw') {
|
||||
await loadSystemIP();
|
||||
allowedOriginsAutoFilled.value = true;
|
||||
@@ -485,6 +570,7 @@ const submit = async () => {
|
||||
webUIPort: form.webUIPort,
|
||||
allowedOrigins: form.agentType === 'openclaw' ? parseAllowedOriginsInput(form.allowedOrigins) : undefined,
|
||||
agentType: form.agentType,
|
||||
accessHost: form.agentType === 'deepseek-harness' ? form.accessHost : undefined,
|
||||
model: showModelConfig.value ? form.model : undefined,
|
||||
accountId: showModelConfig.value ? form.accountId : undefined,
|
||||
token: form.agentType === 'openclaw' ? form.token : undefined,
|
||||
@@ -524,45 +610,19 @@ const handleClose = () => {
|
||||
form.dashboardPassword = '';
|
||||
form.remark = '';
|
||||
form.allowedOrigins = '';
|
||||
form.accessHost = '';
|
||||
form.dockerCompose = '';
|
||||
lastAutoAllowedOrigins.value = '';
|
||||
allowedOriginsAutoFilled.value = true;
|
||||
};
|
||||
|
||||
const openDrawer = async (agentType?: AI.AgentType) => {
|
||||
const targetType =
|
||||
agentType === 'copaw' || agentType === 'hermes-agent' || agentType === 'openclaw' ? agentType : 'openclaw';
|
||||
const targetType = isAgentType(agentType) ? agentType : 'openclaw';
|
||||
form.name = getDefaultAgentName(targetType);
|
||||
open.value = true;
|
||||
form.agentType = targetType;
|
||||
setDefaultWebUIPort();
|
||||
form.token = getRandomStr(32).toLowerCase();
|
||||
ensureDashboardAuth();
|
||||
if (form.agentType === 'copaw') {
|
||||
form.allowedOrigins = '';
|
||||
lastAutoAllowedOrigins.value = '';
|
||||
allowedOriginsAutoFilled.value = true;
|
||||
await loadVersions('copaw');
|
||||
providerOptions.value = [];
|
||||
providerModels.value = {};
|
||||
accountOptions.value = [];
|
||||
return;
|
||||
}
|
||||
if (form.agentType === 'hermes-agent') {
|
||||
form.allowedOrigins = '';
|
||||
lastAutoAllowedOrigins.value = '';
|
||||
allowedOriginsAutoFilled.value = true;
|
||||
await loadVersions('hermes-agent');
|
||||
await loadProviders();
|
||||
await loadAccounts();
|
||||
return;
|
||||
}
|
||||
await loadSystemIP();
|
||||
allowedOriginsAutoFilled.value = true;
|
||||
syncAllowedOriginsWithDefault(true);
|
||||
await loadVersions('openclaw');
|
||||
await loadProviders();
|
||||
await loadAccounts();
|
||||
await handleAgentTypeChange();
|
||||
};
|
||||
|
||||
const openAccountCreate = () => {
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
@@ -193,8 +193,7 @@ const searching = ref(false);
|
||||
const operating = ref('');
|
||||
const keyword = ref('');
|
||||
const marketKeyword = ref('');
|
||||
useSearchState(
|
||||
'ai/agents/agent/config/tabs/plugins',
|
||||
useSearchReset(
|
||||
{ keyword, marketKeyword },
|
||||
{
|
||||
scope: () => agentId.value || undefined,
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -169,8 +169,7 @@ const getDefaultMarketSource = (): HermesSkillMarketSource => {
|
||||
const marketSource = ref<HermesSkillMarketSource>(getDefaultMarketSource());
|
||||
const marketSearched = ref(false);
|
||||
const agentId = ref(0);
|
||||
useSearchState(
|
||||
'ai/agents/agent/config/tabs/skills/hermes',
|
||||
useSearchReset(
|
||||
{ installedKeyword, marketKeyword },
|
||||
{
|
||||
scope: () => agentId.value || undefined,
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -181,8 +181,7 @@ const getDefaultMarketSource = (): SkillMarketSource => {
|
||||
const marketSource = ref<SkillMarketSource>(getDefaultMarketSource());
|
||||
const marketSearched = ref(false);
|
||||
const agentId = ref(0);
|
||||
useSearchState(
|
||||
'ai/agents/agent/config/tabs/skills/openclaw',
|
||||
useSearchReset(
|
||||
{ installedKeyword, marketKeyword },
|
||||
{
|
||||
scope: () => agentId.value || undefined,
|
||||
|
||||
@@ -178,6 +178,9 @@
|
||||
alignment="start"
|
||||
:size="2"
|
||||
>
|
||||
<el-text v-if="row.agentType === 'octop'" type="info" size="small">
|
||||
{{ $t('aiTools.agents.initialCredentials') }}
|
||||
</el-text>
|
||||
<span>{{ row.dashboardUsername || '-' }}</span>
|
||||
<el-space alignment="center">
|
||||
<span>{{ row.dashboardPassword ? '********' : '-' }}</span>
|
||||
@@ -272,7 +275,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { toRef, defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { deleteAgentCheck, pageAgents, resetAgentToken, unbindAgentWebsite, updateAgentRemark } from '@/api/modules/ai';
|
||||
@@ -283,7 +286,7 @@ import { Website } from '@/api/interface/website';
|
||||
import { SearchWithPage } from '@/api/interface';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
|
||||
import AddDialog from '@/views/ai/agents/agent/add/index.vue';
|
||||
import DeleteDialog from '@/views/ai/agents/agent/delete/index.vue';
|
||||
@@ -302,6 +305,8 @@ import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||
import {
|
||||
getAgentProviderDisplayName,
|
||||
getOpenclawAccessScheme,
|
||||
isAgentType,
|
||||
isDeepSeekAccessHost,
|
||||
supportsAgentModelConfig,
|
||||
supportsAgentToken,
|
||||
supportsAgentDashboardAuth,
|
||||
@@ -312,6 +317,8 @@ import NoApp from '@/views/app-store/apps/no-app/index.vue';
|
||||
import openclawIcon from '@/assets/images/ai-agent-openclaw.svg';
|
||||
import copawIcon from '@/assets/images/ai-agent-copaw.svg';
|
||||
import hermesIcon from '@/assets/images/ai-agent-hermes-agent.svg';
|
||||
import deepSeekIcon from '@/assets/images/ai-agent-deepseek-harness.png';
|
||||
import octopIcon from '@/assets/images/ai-agent-octop.png';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { loadOptionalComponent } from '@/extensions/optional';
|
||||
import { useOperateNodeContext } from '@/composables/useOperateNodeContext';
|
||||
@@ -358,8 +365,7 @@ const buttons = [
|
||||
{
|
||||
label: i18n.global.t('menu.config'),
|
||||
click: (row: AI.AgentItem) => openConfig(row),
|
||||
show: (row: AI.AgentItem) =>
|
||||
supportsAgentModelConfig(row.agentType) || supportsAgentDashboardAuth(row.agentType),
|
||||
show: (row: AI.AgentItem) => supportsAgentModelConfig(row.agentType) || row.agentType === 'copaw',
|
||||
disabled: (row: AI.AgentItem) => row.status !== 'Running',
|
||||
},
|
||||
{
|
||||
@@ -400,11 +406,7 @@ const paginationConfig = reactive({
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
useSearchState(
|
||||
'ai/agents/agent/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const checkNoApp = async () => {
|
||||
try {
|
||||
@@ -445,6 +447,10 @@ const getAgentTypeLabel = (agentType: AI.AgentType) => {
|
||||
return 'QwenPaw';
|
||||
case 'hermes-agent':
|
||||
return 'Hermes Agent';
|
||||
case 'deepseek-harness':
|
||||
return 'DeepSeek Harness';
|
||||
case 'octop':
|
||||
return 'Octop';
|
||||
default:
|
||||
return 'OpenClaw';
|
||||
}
|
||||
@@ -456,6 +462,10 @@ const getAgentTypeIcon = (agentType: AI.AgentType) => {
|
||||
return copawIcon;
|
||||
case 'hermes-agent':
|
||||
return hermesIcon;
|
||||
case 'deepseek-harness':
|
||||
return deepSeekIcon;
|
||||
case 'octop':
|
||||
return octopIcon;
|
||||
default:
|
||||
return openclawIcon;
|
||||
}
|
||||
@@ -465,8 +475,7 @@ const openCreate = (agentType?: AI.AgentType) => {
|
||||
if (noApp.value) {
|
||||
return;
|
||||
}
|
||||
const targetType =
|
||||
agentType === 'copaw' || agentType === 'hermes-agent' || agentType === 'openclaw' ? agentType : 'openclaw';
|
||||
const targetType = isAgentType(agentType) ? agentType : 'openclaw';
|
||||
if (addRef.value?.open) {
|
||||
addRef.value.open(targetType);
|
||||
}
|
||||
@@ -477,12 +486,7 @@ const openCreateFromQuery = async () => {
|
||||
if (!shouldOpen) {
|
||||
return;
|
||||
}
|
||||
const agentType =
|
||||
route.query.agentType === 'copaw'
|
||||
? 'copaw'
|
||||
: route.query.agentType === 'hermes-agent'
|
||||
? 'hermes-agent'
|
||||
: 'openclaw';
|
||||
const agentType = isAgentType(route.query.agentType) ? route.query.agentType : 'openclaw';
|
||||
openCreate(agentType);
|
||||
const nextQuery = { ...route.query };
|
||||
delete nextQuery.open;
|
||||
@@ -551,7 +555,12 @@ const openTerminal = (row: AI.AgentItem) => {
|
||||
openTerminalDialog({
|
||||
containerID: row.containerName,
|
||||
title,
|
||||
users: row.agentType === 'hermes-agent' ? ['hermes', 'root'] : ['node', 'root'],
|
||||
users:
|
||||
row.agentType === 'hermes-agent'
|
||||
? ['hermes', 'root']
|
||||
: row.agentType === 'octop'
|
||||
? ['root']
|
||||
: ['node', 'root'],
|
||||
shell: '/bin/bash',
|
||||
initCmd: '',
|
||||
});
|
||||
@@ -583,6 +592,14 @@ const openWorkDir = (row: AI.AgentItem) => {
|
||||
};
|
||||
|
||||
const jumpWebUI = (row: AI.AgentItem) => {
|
||||
if (row.agentType === 'deepseek-harness') {
|
||||
if (!isDeepSeekAccessHost(row.accessHost || '') || !row.webUIPort) {
|
||||
MsgError(i18n.global.t('aiTools.agents.nativeAgentAccessUnavailable'));
|
||||
return;
|
||||
}
|
||||
window.open(`https://${row.accessHost}:${row.webUIPort}`, '_blank', 'noopener,noreferrer');
|
||||
return;
|
||||
}
|
||||
if (dialogPortJumpRef.value?.acceptParams) {
|
||||
dialogPortJumpRef.value.acceptParams({
|
||||
port: row.webUIPort,
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { toRef, onMounted, reactive, ref } from 'vue';
|
||||
import { deleteAgentAccount, getAgentProviders, pageAgentAccounts } from '@/api/modules/ai';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
@@ -111,11 +111,7 @@ const paginationConfig = reactive({
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
useSearchState(
|
||||
'ai/agents/model/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const search = async () => {
|
||||
const req: AI.AgentAccountSearch = {
|
||||
|
||||
@@ -98,8 +98,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRef } from 'vue';
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import {
|
||||
deleteMcpServer,
|
||||
@@ -131,18 +129,11 @@ const bindDomainRef = ref();
|
||||
const configRef = ref();
|
||||
const items = ref<AI.McpServer[]>([]);
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'mcp-server-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('mcp-server-page-size')) || 20,
|
||||
total: 0,
|
||||
});
|
||||
useSearchState(
|
||||
'ai/mcp/server/index',
|
||||
{
|
||||
page: toRef(paginationConfig, 'currentPage'),
|
||||
pageSize: toRef(paginationConfig, 'pageSize'),
|
||||
},
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
|
||||
const getUrl = (row: AI.McpServer) => {
|
||||
if (row.outputTransport == 'sse') {
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import AppStatus from '@/components/app-status/index.vue';
|
||||
import AddDialog from '@/views/ai/model/ollama/add/index.vue';
|
||||
import Conn from '@/views/ai/model/ollama/conn/index.vue';
|
||||
@@ -231,16 +231,13 @@ const bindDomainRef = ref();
|
||||
const taskLogRef = ref();
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'model-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('page-size')) || 20,
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchState(
|
||||
'ai/model/ollama/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
const appInstallID = ref(0);
|
||||
|
||||
const opRef = ref();
|
||||
|
||||
@@ -77,8 +77,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRef } from 'vue';
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import OperateDialog from './operate/index.vue';
|
||||
import ComposeLogs from '@/components/log/compose/index.vue';
|
||||
import PortJumpDialog from '@/components/port-jump/index.vue';
|
||||
@@ -103,14 +101,6 @@ const paginationConfig = reactive({
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
useSearchState(
|
||||
'ai/model/tensorrt/index',
|
||||
{
|
||||
page: toRef(paginationConfig, 'currentPage'),
|
||||
pageSize: toRef(paginationConfig, 'pageSize'),
|
||||
},
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
const searchName = ref();
|
||||
const opRef = ref();
|
||||
const dialogRef = ref();
|
||||
|
||||
@@ -70,10 +70,18 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { App } from '@/api/interface/app';
|
||||
import { toRef, onMounted, reactive, ref } from 'vue';
|
||||
import { searchApp, syncApp, syncCutomAppStore, syncLocalApp, getCurrentNodeCustomAppConfig } from '@/api/modules/app';
|
||||
import {
|
||||
searchApp,
|
||||
syncApp,
|
||||
syncCutomAppStore,
|
||||
syncLocalApp,
|
||||
getCurrentNodeCustomAppConfig,
|
||||
getLocalAppSyncConfig,
|
||||
dispatchLocalAppSync,
|
||||
} from '@/api/modules/app';
|
||||
import Install from '../detail/install/index.vue';
|
||||
import router from '@/routers';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
@@ -88,11 +96,12 @@ import AppCard from '@/views/app-store/apps/app/index.vue';
|
||||
import MainDiv from '@/components/main-div/index.vue';
|
||||
import { jumpToInstall } from '@/utils/app';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { isProductPro, isOffline, isMobile, isEnterprise } = useGlobalStore();
|
||||
const { isProductPro, isOffline, isMobile, isEnterprise, isMaster, isXpackOrEE, isAdmin } = useGlobalStore();
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'app-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 60,
|
||||
pageSize: Number(localStorage.getItem('app-page-size')) || 60,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
@@ -104,13 +113,8 @@ const req = reactive({
|
||||
resource: 'all',
|
||||
showCurrentArch: false,
|
||||
});
|
||||
useSearchState(
|
||||
'app-store/apps/index',
|
||||
{
|
||||
search: toRef(req, 'name'),
|
||||
page: toRef(paginationConfig, 'currentPage'),
|
||||
pageSize: toRef(paginationConfig, 'pageSize'),
|
||||
},
|
||||
useSearchReset(
|
||||
{ search: toRef(req, 'name'), page: toRef(paginationConfig, 'currentPage') },
|
||||
{ onRevisit: () => search(req) },
|
||||
);
|
||||
|
||||
@@ -137,6 +141,7 @@ const search = async (req: App.AppReq) => {
|
||||
loading.value = true;
|
||||
req.pageSize = paginationConfig.pageSize;
|
||||
req.page = paginationConfig.currentPage;
|
||||
localStorage.setItem('app-page-size', req.pageSize + '');
|
||||
|
||||
const customReq = {
|
||||
page: req.page,
|
||||
@@ -204,21 +209,27 @@ const sync = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const syncLocal = () => {
|
||||
const syncLocal = async () => {
|
||||
const taskID = newUUID();
|
||||
const syncReq = {
|
||||
taskID: taskID,
|
||||
};
|
||||
syncing.value = true;
|
||||
syncLocalApp(syncReq)
|
||||
.then(() => {
|
||||
try {
|
||||
const dispatch =
|
||||
isMaster.value && isXpackOrEE.value && isAdmin.value && (await getLocalAppSyncConfig()).data.enabled;
|
||||
if (dispatch) {
|
||||
await dispatchLocalAppSync(syncReq);
|
||||
taskLogRef.value.openWithTaskID(taskID, true, 'local');
|
||||
} else {
|
||||
await syncLocalApp(syncReq);
|
||||
openTaskLog(taskID);
|
||||
canUpdate.value = false;
|
||||
search(req);
|
||||
})
|
||||
.finally(() => {
|
||||
syncing.value = false;
|
||||
});
|
||||
}
|
||||
canUpdate.value = false;
|
||||
search(req);
|
||||
} finally {
|
||||
syncing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const changeTag = (key: string) => {
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import AppCard from '@/views/app-store/installed/app/card.vue';
|
||||
import Backups from '@/components/backup/index.vue';
|
||||
import Uploads from '@/components/upload/index.vue';
|
||||
@@ -181,8 +181,9 @@ const sortContainer = ref();
|
||||
let sortableInstance: Sortable | null = null;
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'app-installed-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('app-installed-page-size')) || 20,
|
||||
total: 0,
|
||||
});
|
||||
const open = ref(false);
|
||||
@@ -210,13 +211,8 @@ const searchReq = reactive({
|
||||
update: false,
|
||||
sync: false,
|
||||
});
|
||||
useSearchState(
|
||||
'app-store/installed/index',
|
||||
{
|
||||
search: toRef(searchReq, 'name'),
|
||||
page: toRef(paginationConfig, 'currentPage'),
|
||||
pageSize: toRef(paginationConfig, 'pageSize'),
|
||||
},
|
||||
useSearchReset(
|
||||
{ search: toRef(searchReq, 'name'), page: toRef(paginationConfig, 'currentPage') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
const router = useRouter();
|
||||
@@ -242,6 +238,8 @@ const search = async () => {
|
||||
searchReq.page = paginationConfig.currentPage;
|
||||
searchReq.pageSize = paginationConfig.pageSize;
|
||||
|
||||
localStorage.setItem('app-installed-page-size', String(searchReq.pageSize));
|
||||
|
||||
const res = await searchAppInstalled(searchReq);
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
|
||||
@@ -522,7 +522,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { computed, h, ref } from 'vue';
|
||||
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
|
||||
import ContainerLog from '@/components/log/container/index.vue';
|
||||
@@ -577,7 +577,7 @@ const dialogBackupRef = ref();
|
||||
const uploadRef = ref();
|
||||
|
||||
const searchName = ref('');
|
||||
useSearchState('container/compose/index', { search: searchName }, { onRevisit: () => search() });
|
||||
useSearchReset({ search: searchName }, { onRevisit: () => search() });
|
||||
const includeAppStore = ref(localStorage.getItem('includeAppStore') !== 'false');
|
||||
const showType = ref('compose');
|
||||
const containerStats = ref<any[]>([]);
|
||||
|
||||
@@ -474,7 +474,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import PruneDialog from '@/views/container/container/prune/index.vue';
|
||||
import RenameDialog from '@/views/container/container/rename/index.vue';
|
||||
import UpgradeDialog from '@/views/container/container/upgrade/index.vue';
|
||||
@@ -527,14 +527,16 @@ const portFilter = ref('');
|
||||
const showIPv6Ports = ref(true);
|
||||
const selects = ref<any>([]);
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'container-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('container-page-size')) || 20,
|
||||
total: 0,
|
||||
state: 'all',
|
||||
orderBy: 'name',
|
||||
order: 'ascending',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
const dialogUpgradeRef = ref();
|
||||
const dialogCommitRef = ref();
|
||||
const dialogPortJumpRef = ref();
|
||||
@@ -550,19 +552,6 @@ const taskLogRef = ref();
|
||||
|
||||
const tags = ref([]);
|
||||
const activeTag = ref('all');
|
||||
useSearchState(
|
||||
'container/container/index',
|
||||
{
|
||||
search: searchName,
|
||||
page: toRef(paginationConfig, 'currentPage'),
|
||||
pageSize: toRef(paginationConfig, 'pageSize'),
|
||||
state: toRef(paginationConfig, 'state'),
|
||||
activeTag,
|
||||
orderBy: toRef(paginationConfig, 'orderBy'),
|
||||
order: toRef(paginationConfig, 'order'),
|
||||
},
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
|
||||
const activeDropdownContainerId = ref('');
|
||||
const statFields = [
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { toRef, reactive, ref, computed } from 'vue';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { newUUID } from '@/utils/id';
|
||||
@@ -192,20 +192,16 @@ const data = ref();
|
||||
const names = ref();
|
||||
const repos = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'container-image-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('container-image-page-size')) || 20,
|
||||
total: 0,
|
||||
name: '',
|
||||
orderBy: 'createdAt',
|
||||
order: 'null',
|
||||
});
|
||||
useSearchState(
|
||||
'container/image/index',
|
||||
{
|
||||
search: toRef(paginationConfig, 'name'),
|
||||
page: toRef(paginationConfig, 'currentPage'),
|
||||
pageSize: toRef(paginationConfig, 'pageSize'),
|
||||
},
|
||||
useSearchReset(
|
||||
{ search: toRef(paginationConfig, 'name'), page: toRef(paginationConfig, 'currentPage') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
const columns = ref([]);
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import CreateDialog from '@/views/container/network/create/index.vue';
|
||||
import DetailDrawer from '@/views/container/network/detail/index.vue';
|
||||
import { toRef, reactive, ref } from 'vue';
|
||||
@@ -115,16 +115,13 @@ const taskLogRef = ref();
|
||||
const data = ref();
|
||||
const selects = ref<any>([]);
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'container-network-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('container-network-page-size')) || 20,
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchState(
|
||||
'container/network/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const opRef = ref();
|
||||
const isActive = ref(false);
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import OperatorDialog from '@/views/container/repo/operator/index.vue';
|
||||
import { toRef, reactive, ref } from 'vue';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
@@ -72,16 +72,13 @@ const loading = ref();
|
||||
const data = ref();
|
||||
const selects = ref<any>([]);
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'image-repo-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('image-repo-page-size')) || 20,
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchState(
|
||||
'container/repo/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const opRef = ref();
|
||||
const confirmDialog = ref();
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { toRef, reactive, ref } from 'vue';
|
||||
import { dateFormat, getCurrentDateFormatted } from '@/utils/date';
|
||||
import { downloadWithContent } from '@/utils/file';
|
||||
@@ -105,16 +105,13 @@ const isActive = ref(false);
|
||||
const isExist = ref(false);
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'compose-template-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('compose-template-page-size')) || 20,
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchState(
|
||||
'container/template/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const search = async () => {
|
||||
if (!isActive.value || !isExist.value) {
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import CreateDialog from '@/views/container/volume/create/index.vue';
|
||||
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
|
||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||
@@ -152,16 +152,13 @@ const opRef = ref();
|
||||
const data = ref();
|
||||
const selects = ref<any>([]);
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'container-volume-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('container-volume-page-size')) || 20,
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchState(
|
||||
'container/volume/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
const isActive = ref(false);
|
||||
const isExist = ref(false);
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import Records from '@/views/cronjob/cronjob/record/index.vue';
|
||||
import Backups from '@/views/cronjob/cronjob/backup/index.vue';
|
||||
import Import from '@/views/cronjob/cronjob/import/index.vue';
|
||||
@@ -310,8 +310,9 @@ const groupOptions = ref();
|
||||
const dialogGroupRef = ref();
|
||||
const pageState = usePageState(() => ({
|
||||
paginationConfig: {
|
||||
cacheSizeKey: 'cronjob-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('cronjob-page-size')) || 20,
|
||||
total: 0,
|
||||
orderBy: 'createdAt',
|
||||
order: 'null',
|
||||
@@ -322,11 +323,7 @@ const pageState = usePageState(() => ({
|
||||
}));
|
||||
const paginationConfig = pageState.paginationConfig;
|
||||
const { defaultGroupID, searchName, searchGroupID } = toRefs(pageState);
|
||||
useSearchState(
|
||||
'cronjob/cronjob/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const search = async (column?: any) => {
|
||||
if (column) {
|
||||
|
||||
@@ -256,7 +256,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { panelCacheKey, writePanelCache, readCachedPageSize } from '@/utils/panel-cache';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
import { Cronjob } from '@/api/interface/cronjob';
|
||||
@@ -310,7 +309,7 @@ const formatInterval = (interval?: number) => {
|
||||
};
|
||||
|
||||
const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
let itemSize = readCachedPageSize(searchInfo.cacheSizeKey);
|
||||
let itemSize = Number(localStorage.getItem(searchInfo.cacheSizeKey));
|
||||
if (itemSize) {
|
||||
searchInfo.pageSize = itemSize;
|
||||
}
|
||||
@@ -342,7 +341,7 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
|
||||
const handleSizeChange = (val: number) => {
|
||||
searchInfo.pageSize = val;
|
||||
writePanelCache('pagination', panelCacheKey(`size:${searchInfo.cacheSizeKey}`), val);
|
||||
localStorage.setItem(searchInfo.cacheSizeKey, val + '');
|
||||
search(true);
|
||||
};
|
||||
const handleCurrentChange = (val: number) => {
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { dateFormat, getCurrentDateFormatted } from '@/utils/date';
|
||||
import { deepCopy } from '@/utils/misc';
|
||||
import { newUUID } from '@/utils/id';
|
||||
@@ -156,16 +156,13 @@ const scriptSync = ref();
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'script-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('script-page-size')) || 20,
|
||||
total: 0,
|
||||
});
|
||||
const searchInfo = ref<string>('');
|
||||
useSearchState(
|
||||
'cronjob/library/index',
|
||||
{ search: searchInfo, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchInfo, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
const group = ref<string>('');
|
||||
const groupOptions = ref();
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { toRef, nextTick, onMounted, reactive, ref } from 'vue';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { getRandomStr } from '@/utils/id';
|
||||
@@ -392,17 +392,14 @@ const dbOptionsRemote = ref<Array<Database.DatabaseOption>>([]);
|
||||
|
||||
const tableRows = ref<Array<Database.MongodbDBInfo>>([]);
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'mongodb-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('mongodb-page-size')) || 20,
|
||||
total: 0,
|
||||
orderBy: 'createdAt',
|
||||
order: 'null',
|
||||
});
|
||||
useSearchState(
|
||||
'database/mongodb/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { toRef, onMounted, reactive, ref } from 'vue';
|
||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||
@@ -93,18 +93,15 @@ const deleteRef = ref();
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'mongodb-remote-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('mongodb-remote-page-size')) || 20,
|
||||
total: 0,
|
||||
orderBy: 'createdAt',
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchState(
|
||||
'database/mongodb/remote/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
|
||||
@@ -226,7 +226,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import BindDialog from '@/views/database/mysql/bind/index.vue';
|
||||
import UserDialog from '@/views/database/mysql/user/index.vue';
|
||||
import OperateDialog from '@/views/database/mysql/create/index.vue';
|
||||
@@ -293,18 +293,15 @@ const dialogPortJumpRef = ref();
|
||||
const data = ref<Database.MysqlDBInfo[]>([]);
|
||||
let grantSummaryRequestID = 0;
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'mysql-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('mysql-page-size')) || 20,
|
||||
total: 0,
|
||||
orderBy: 'createdAt',
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchState(
|
||||
'database/mysql/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const mysqlContainer = ref();
|
||||
const mysqlStatus = ref();
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { toRef, onMounted, reactive, ref } from 'vue';
|
||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||
@@ -92,18 +92,15 @@ const deleteRef = ref();
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'mysql-remote-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('mysql-remote-page-size')) || 20,
|
||||
total: 0,
|
||||
orderBy: 'createdAt',
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchState(
|
||||
'database/mysql/remote/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
|
||||
@@ -241,7 +241,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import BindDialog from '@/views/database/postgresql/bind/index.vue';
|
||||
import OperateDialog from '@/views/database/postgresql/create/index.vue';
|
||||
import DeleteDialog from '@/views/database/postgresql/delete/index.vue';
|
||||
@@ -305,18 +305,15 @@ const dialogPortJumpRef = ref();
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'postgresql-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('postgresql-page-size')) || 20,
|
||||
total: 0,
|
||||
orderBy: 'createdAt',
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchState(
|
||||
'database/postgresql/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const postgresqlContainer = ref();
|
||||
const postgresqlStatus = ref();
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { toRef, onMounted, reactive, ref } from 'vue';
|
||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||
@@ -92,18 +92,15 @@ const deleteRef = ref();
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'postgresql-remote-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('postgresql-remote-page-size')) || 20,
|
||||
total: 0,
|
||||
orderBy: 'createdAt',
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchState(
|
||||
'database/postgresql/remote/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSearchState } from '@/utils/search-reset';
|
||||
import { useSearchReset } from '@/composables/useSearchReset';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { toRef, onMounted, reactive, ref } from 'vue';
|
||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||
@@ -91,18 +91,15 @@ const deleteRef = ref();
|
||||
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'redis-remote-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: Number(localStorage.getItem('redis-remote-page-size')) || 20,
|
||||
total: 0,
|
||||
orderBy: 'createdAt',
|
||||
order: 'null',
|
||||
});
|
||||
const searchName = ref();
|
||||
useSearchState(
|
||||
'database/redis/remote/index',
|
||||
{ search: searchName, page: toRef(paginationConfig, 'currentPage'), pageSize: toRef(paginationConfig, 'pageSize') },
|
||||
{ onRevisit: () => search() },
|
||||
);
|
||||
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
|
||||
|
||||
const search = async (column?: any) => {
|
||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||
|
||||
@@ -133,7 +133,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { readCachedPageSize } from '@/utils/panel-cache';
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import { Database } from '@/api/interface/database';
|
||||
import { redisPersistenceConf, updateRedisPersistenceConf } from '@/api/modules/database';
|
||||
@@ -189,7 +188,7 @@ const confirmDialogRef = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'redis-backup-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: readCachedPageSize('redis-backup-page-size') || 20,
|
||||
pageSize: Number(localStorage.getItem('redis-backup-page-size')) || 20,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user