Compare commits

..
Author SHA1 Message Date
guoyuqi 1313462805 refactor: refactor user info 2026-10-10 10:27:47 +08:00
Yuki Guoandssongliu 486cd5d0ad feat(redis): enable remote terminal through tracked background tasks (#14012)
* feat(redis): enable remote terminal through tracked background tasks

* feat(redis): enable remote terminal through tracked background tasks

* refactor(redis): remove redundant CLI status tracking

* refactor(redis): retain existing task execution flow

---------

Co-authored-by: ssongliu <sloooop1x@gmail.com>
2026-10-09 18:20:51 +08:00
ssongliu 00ce5a7b36 fix(firewall): preserve IPv6 RA protection and simplify error messages (#14015) 2026-10-09 17:45:57 +08:00
ssongliu c17f055e06 fix: preserve VM task timeout cleanup and terminate command groups (#14008) 2026-10-09 17:45:41 +08:00
王贺 f22df4b29c fix(file): stabilize closable tab width to avoid layout jitter (#14013)
The closable card tabs animated the close icon width between active and inactive tabs, so each tab switch reflowed the tab bar and caused the page to jitter. Reserve the close icon slot and toggle it with opacity instead, matching the terminal tab implementation.
2026-10-09 17:16:10 +08:00
Yuki Guoandssongliu b9bab90a14 fix: fix clean network (#14009)
* fix: fix clean network

* refactor(container): simplify network cleanup

---------

Co-authored-by: ssongliu <sloooop1x@gmail.com>
2026-10-09 17:14:50 +08:00
96 changed files with 940 additions and 455 deletions
+10 -4
View File
@@ -86,17 +86,23 @@ func (b *BaseApi) CheckHasCli(c *gin.Context) {
// @Tags Database Redis
// @Summary Install redis-cli
// @Success 200
// @Accept json
// @Param request body dto.RedisCliInstall true "request"
// @Success 200 {string} string
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/redis/install/cli [post]
func (b *BaseApi) InstallCli(c *gin.Context) {
if err := redisService.InstallCli(); err != nil {
var req dto.RedisCliInstall
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := redisService.InstallCli(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
helper.SuccessWithData(c, data)
}
// @Tags Database Redis
+4
View File
@@ -22,6 +22,10 @@ type DBBaseInfo struct {
Port int64 `json:"port"`
}
type RedisCliInstall struct {
TaskID string `json:"taskID" validate:"omitempty,uuid"`
}
// mysql
type MysqlDBSearch struct {
PageInfo
+6 -7
View File
@@ -50,13 +50,12 @@ type FirewallBackendOption struct {
}
type FirewallBackendFamilyStatus struct {
Partial bool `json:"partial"`
Available bool `json:"available"`
Initialized bool `json:"initialized"`
Bound bool `json:"bound"`
Reason string `json:"reason,omitempty"`
ForwardPolicy string `json:"forwardPolicy,omitempty"`
RAInterfaces []string `json:"raInterfaces,omitempty"`
Partial bool `json:"partial"`
Available bool `json:"available"`
Initialized bool `json:"initialized"`
Bound bool `json:"bound"`
Reason string `json:"reason,omitempty"`
RAInterfaces []string `json:"raInterfaces,omitempty"`
}
type FirewallBackendGroup struct {
+9 -9
View File
@@ -383,11 +383,6 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
}
func (u *ContainerService) Prune(req dto.ContainerPrune) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
}
defer client.Close()
name := ""
switch req.PruneType {
case "container":
@@ -408,6 +403,14 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
}
taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
if err := t.TaskCtx.Err(); err != nil {
return err
}
client, err := docker.NewDockerClient()
if err != nil {
return err
}
defer client.Close()
pruneFilters := filters.NewArgs()
if req.WithTagAll {
pruneFilters.Add("dangling", "false")
@@ -431,10 +434,7 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
}
SpaceReclaimed = int(rep.SpaceReclaimed)
case "network":
_, err := client.NetworksPrune(context.Background(), pruneFilters)
if err != nil {
return err
}
return cleanUnusedNetworks(t, client)
case "volume":
versions, err := client.ServerVersion(context.Background())
if err != nil {
+119
View File
@@ -2,16 +2,24 @@ package service
import (
"context"
"errors"
"fmt"
"sort"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/docker/errdefs"
)
var networkCleanupSlot = make(chan struct{}, 1)
func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) {
client, err := docker.NewDockerClient()
if err != nil {
@@ -172,3 +180,114 @@ func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error {
}
return nil
}
func cleanUnusedNetworks(t *task.Task, cli *client.Client) error {
ctx := t.TaskCtx
select {
case networkCleanupSlot <- struct{}{}:
defer func() { <-networkCleanupSlot }()
case <-ctx.Done():
return ctx.Err()
}
if err := ctx.Err(); err != nil {
return err
}
networks, err := cli.NetworkList(ctx, network.ListOptions{})
if err != nil {
if ctx.Err() != nil {
return ctx.Err()
}
return err
}
deleted, skipped, failed := 0, 0, 0
defer func() {
t.Log(i18n.GetMsgWithMap("NetworkCleanupSummary", map[string]interface{}{
"deleted": deleted, "skipped": skipped, "failed": failed,
}))
}()
var used map[string]bool
sort.Slice(networks, func(i, j int) bool { return networks[i].Name < networks[j].Name })
for _, n := range networks {
if err := ctx.Err(); err != nil {
return err
}
switch n.Name {
case "none", "host", "bridge", "1panel-network":
skipped++
continue
}
if n.Scope != "local" || n.Ingress || n.ConfigOnly {
skipped++
continue
}
values := map[string]interface{}{"name": n.Name, "id": n.ID}
if used == nil {
containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
if err != nil {
if ctx.Err() != nil {
return ctx.Err()
}
return err
}
used = make(map[string]bool)
for _, c := range containers {
if c.NetworkSettings == nil {
continue
}
for name, endpoint := range c.NetworkSettings.Networks {
used[name] = true
if endpoint != nil {
used[endpoint.NetworkID] = true
}
}
}
}
if used[n.Name] || used[n.ID] {
skipped++
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
continue
}
inspected, err := cli.NetworkInspect(ctx, n.ID, network.InspectOptions{})
if err != nil {
if ctx.Err() != nil {
return ctx.Err()
}
if errdefs.IsNotFound(err) {
skipped++
} else {
failed++
t.Logf("Failed to inspect network [%s] (%s): %v", n.Name, n.ID, err)
}
continue
}
if len(inspected.Containers) > 0 {
skipped++
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
continue
}
if err := cli.NetworkRemove(ctx, n.ID); err != nil {
if ctx.Err() != nil {
return ctx.Err()
}
switch {
case errdefs.IsNotFound(err):
skipped++
case errdefs.IsConflict(err):
skipped++
t.Log(i18n.GetMsgWithMap("NetworkCleanupConnected", values))
default:
failed++
t.Logf("Failed to remove network [%s] (%s): %v", n.Name, n.ID, err)
}
continue
}
deleted++
}
if err := ctx.Err(); err != nil {
return err
}
if failed > 0 {
return errors.New(i18n.GetMsgByKey("NetworkCleanupPartialFailure"))
}
return nil
}
+63 -19
View File
@@ -8,6 +8,12 @@ import (
"os"
"os/exec"
"strings"
"sync"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/google/uuid"
"gorm.io/gorm"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/global"
@@ -17,12 +23,15 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
"github.com/docker/docker/api/types/container"
dockerclient "github.com/docker/docker/client"
_ "github.com/go-sql-driver/mysql"
)
type RedisService struct{}
// The CLI container is shared by all remote Redis databases on this node.
var redisCliInstallMutex sync.Mutex
type IRedisService interface {
UpdateConf(req dto.RedisConfUpdate) error
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
@@ -33,7 +42,7 @@ type IRedisService interface {
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
CheckHasCli() bool
InstallCli() error
InstallCli(req dto.RedisCliInstall) (string, error)
}
func NewIRedisService() IRedisService {
@@ -61,30 +70,65 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
}
func (u *RedisService) CheckHasCli() bool {
client, err := docker.NewDockerClient()
if err != nil {
return false
}
defer client.Close()
containerLists, err := client.ContainerList(context.Background(), container.ListOptions{})
if err != nil {
return false
}
for _, item := range containerLists {
if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" {
return true
}
}
return false
installed, _ := u.checkCliInstalled()
return installed
}
func (u *RedisService) InstallCli() error {
func (u *RedisService) checkCliInstalled() (bool, error) {
client, err := docker.NewDockerClient()
if err != nil {
return false, err
}
defer client.Close()
info, err := client.ContainerInspect(context.Background(), "1Panel-redis-cli-tools")
if dockerclient.IsErrNotFound(err) {
return false, nil
}
if err != nil {
return false, err
}
return info.ContainerJSONBase != nil && info.State != nil && info.State.Running, nil
}
func (u *RedisService) InstallCli(req dto.RedisCliInstall) (string, error) {
if !redisCliInstallMutex.TryLock() {
return "", buserr.New("TaskIsExecuting")
}
defer redisCliInstallMutex.Unlock()
item := dto.ContainerOperate{
TaskID: req.TaskID,
Name: "1Panel-redis-cli-tools",
Image: "redis:7.4.4",
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
}
return NewIContainerService().ContainerCreate(item, false)
running, err := taskRepo.GetFirst(
repo.WithByName(task.GetTaskName(item.Name, task.TaskCreate, task.TaskScopeContainer)),
repo.WithByType(task.TaskScopeContainer),
taskRepo.WithByStatus(constant.StatusExecuting),
)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return "", err
}
if running.ID != "" {
return running.ID, nil
}
installed, err := u.checkCliInstalled()
if err != nil || installed {
return "", err
}
if item.TaskID == "" {
item.TaskID = uuid.NewString()
}
if _, err := taskRepo.GetFirst(taskRepo.WithByID(item.TaskID)); !errors.Is(err, gorm.ErrRecordNotFound) {
if err != nil {
return "", err
}
return "", buserr.New("TaskIsExecuting")
}
if err := NewIContainerService().ContainerCreate(item, true); err != nil {
return "", err
}
return item.TaskID, nil
}
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
+1 -3
View File
@@ -108,9 +108,7 @@ func (s *FirewallService) LoadBaseInfo(chainGroup string) (dto.FirewallSubsystem
status.Reason = constant.FirewallBackendNotInstalled
return status, nil
}
status.IsExist = true
status.Message = err.Error()
return status, nil
return status, err
}
status.IsExist = true
runtimeStatus, err := lifecycle.LoadStatus(client)
+3 -1
View File
@@ -1185,15 +1185,17 @@ func loadForwardingFirewallOverview(manager forwarding.Adapter, families []strin
}
ipv6, ipv6Err := loadForwardingFamilyInfo(manager, constant.FirewallFamilyIPv6)
if ipv6.Available {
interfaces, err := forwarding.IPv6RAInterfaces(os.ReadFile)
interfaces, err := forwarding.IPv6RAInterfaces(os.ReadFile, forwarding.LoadDockerIPv4BridgePorts)
var pathError *os.PathError
switch {
case errors.As(err, &pathError) && errors.Is(err, os.ErrNotExist) && pathError.Path == "/proc/net/if_inet6":
ipv6.Available, ipv6.Initialized, ipv6.Bound = false, false, false
case err != nil:
global.LOG.Warnf("IPv6 forwarding RA check failed: %v", err)
ipv6.Reason = "ipv6_ra_check_failed"
ipv6.Bound = false
case len(interfaces) > 0:
global.LOG.Warnf("IPv6 forwarding RA risk: interfaces %s may depend on RA/SLAAC with accept_ra=1", strings.Join(interfaces, ", "))
ipv6.Reason, ipv6.RAInterfaces = "ipv6_ra_required", interfaces
ipv6.Bound = false
case !ipv6.Bound:
-43
View File
@@ -9,13 +9,10 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
@@ -82,23 +79,6 @@ func (s *ForwardingService) LoadBaseInfo(ctx context.Context) (dto.FirewallSubsy
baseInfo.PingStatus = firewall.LoadPingStatus()
baseInfo.IsInit, baseInfo.IsBind = status.IsInit, status.IsBind
baseInfo.IPv4, baseInfo.IPv6 = status.IPv4, status.IPv6
for _, family := range []struct {
command string
status *dto.FirewallBackendFamilyStatus
}{
{"iptables", &baseInfo.IPv4},
{"ip6tables", &baseInfo.IPv6},
} {
if family.command == "ip6tables" && !baseInfo.IPv6Enabled {
continue
}
policy, err := loadForwardPolicy(ctx, family.command)
if err != nil {
global.LOG.Warnf("inspect %s FORWARD policy: %v", family.command, err)
continue
}
family.status.ForwardPolicy = policy
}
return baseInfo, nil
}
@@ -308,26 +288,3 @@ func (s *ForwardingService) Restore(ctx context.Context) error {
func NewIForwardingService() IForwardingService {
return newForwardingService()
}
func loadForwardPolicy(ctx context.Context, command string) (string, error) {
if !cmd.Which(command) {
command += "-nft"
if !cmd.Which(command) {
return "", nil
}
}
output, err := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(5*time.Second)).RunWithOptionalSudoAndStdout(command, "-t", "filter", "-w", "2", "-S", "FORWARD")
if err != nil {
return "", err
}
for _, line := range strings.Split(output, "\n") {
fields := strings.Fields(line)
if len(fields) == 3 && fields[0] == "-P" && fields[1] == "FORWARD" {
if fields[2] != "ACCEPT" && fields[2] != "DROP" {
return "", fmt.Errorf("unexpected FORWARD policy: %s", fields[2])
}
return fields[2], nil
}
}
return "", errors.New("FORWARD default policy was not found")
}
+9
View File
@@ -87,6 +87,7 @@ const (
TaskScopeCronjob = "Cronjob"
TaskScopeClam = "Clam"
TaskScopeSystem = "System"
TaskScopeVm = "VirtualMachine"
TaskScopeFirewall = "Firewall"
TaskScopeAppStore = "AppStore"
TaskScopeSnapshot = "Snapshot"
@@ -258,7 +259,15 @@ func (s *SubTask) Execute() error {
case <-ctx.Done():
s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName))
err = errors.New("timeout!")
if s.RootTask.Task.Type == TaskScopeVm {
<-done
}
case err = <-done:
if s.RootTask.Task.Type == TaskScopeVm && ctx.Err() != nil {
s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName))
err = errors.New("timeout!")
break
}
if err != nil {
s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", subTaskName, err))
if err.Error() == i18n.GetMsgByKey("ErrShutDown") {
+5
View File
@@ -300,6 +300,9 @@ BuildCache: 'Build Cache'
Volume: 'Volume'
Network: "Network"
PruneStart: 'Cleaning in progress'
NetworkCleanupConnected: "Skipped network [{{ .name }}] ({{ .id }}): containers connected or network in use; not deleted"
NetworkCleanupSummary: "Network cleanup finished: deleted {{ .deleted }}, skipped {{ .skipped }}, failed {{ .failed }}"
NetworkCleanupPartialFailure: "Some networks could not be cleaned; see the task log"
ContainerBackupPrepare: 'Prepare container backup workspace'
ContainerBackupStop: 'Stop container before backup'
ContainerBackupInspect: 'Backup container inspect and network'
@@ -718,6 +721,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 forwarding is disabled. Set net.ipv4.ip_f
ErrFirewallRuleSavedApplyFailed: "The new rule configuration was saved, but applying it to the firewall failed. Retry by synchronizing: {{ .detail }}"
ErrFirewallRuleConflict: "A UFW rule with the same match conditions and a different action already exists. Edit the existing rule instead."
ErrForwardInterfaceNotFound: "Network interface {{ .name }} specified by the forwarding rule does not exist. Check the interface name and retry."
ErrIPv6RARisk: "IPv6 forwarding is blocked to prevent possible network loss. See the documentation and system logs for details."
ErrIPv6RACheckFailed: "IPv6 forwarding is blocked because the network configuration could not be checked. See the system logs for details."
ErrSSHPublicKey: "Enter a valid SSH public key"
ErrSSHPrivateKey: "Invalid SSH private key or incorrect passphrase; encrypted keys require the original passphrase"
+5
View File
@@ -300,6 +300,9 @@ BuildCache: 'Caché de compilación'
Volume: 'Volumen de almacenamiento'
Network: 'Red'
PruneStart: 'Limpiando en progreso espere...'
NetworkCleanupConnected: "Se omitió la red [{{ .name }}] ({{ .id }}): hay contenedores conectados o la red está en uso; no se eliminó"
NetworkCleanupSummary: "Limpieza de redes finalizada: eliminadas {{ .deleted }}, omitidas {{ .skipped }}, fallidas {{ .failed }}"
NetworkCleanupPartialFailure: "No se pudieron limpiar algunas redes; consulte el registro de la tarea"
ContainerBackupPrepare: 'Preparar espacio de respaldo del contenedor'
ContainerBackupStop: 'Detener contenedor antes del respaldo'
ContainerBackupInspect: 'Respaldar inspect y red del contenedor'
@@ -718,6 +721,8 @@ ErrDockerIPv4ForwardingDisabled: "El reenvío IPv4 está desactivado. Configure
ErrFirewallRuleSavedApplyFailed: "Se guardó la configuración de la nueva regla, pero no se pudo aplicar al cortafuegos. Vuelva a intentarlo mediante la sincronización: {{ .detail }}"
ErrFirewallRuleConflict: "Ya existe una regla UFW con las mismas condiciones y una acción diferente. Edite la regla existente."
ErrForwardInterfaceNotFound: "La interfaz de red {{ .name }} indicada en la regla de reenvío no existe. Compruebe el nombre e inténtelo de nuevo."
ErrIPv6RARisk: "El reenvío IPv6 está bloqueado para evitar una posible pérdida de conectividad. Consulte la documentación y los registros del sistema."
ErrIPv6RACheckFailed: "El reenvío IPv6 está bloqueado porque no se pudo comprobar la configuración de red. Consulte los registros del sistema."
ErrSSHPublicKey: "Introduce una clave pública SSH válida"
ErrSSHPrivateKey: "Clave privada SSH no válida o contraseña incorrecta; las claves cifradas requieren la contraseña original"
+5
View File
@@ -300,6 +300,9 @@ BuildCache: 'کش ساخت'
Volume: 'حجم'
Network: "شبکه"
PruneStart: 'پاک‌سازی در حال انجام است'
NetworkCleanupConnected: "از شبکه [{{ .name }}] ({{ .id }}) صرف‌نظر شد: کانتینر متصل دارد یا در حال استفاده است؛ حذف نشد"
NetworkCleanupSummary: "پاک‌سازی شبکه‌ها پایان یافت: حذف‌شده {{ .deleted }}، ردشده {{ .skipped }}، ناموفق {{ .failed }}"
NetworkCleanupPartialFailure: "پاک‌سازی برخی شبکه‌ها ناموفق بود؛ گزارش وظیفه را بررسی کنید"
ContainerBackupPrepare: 'آماده‌سازی فضای کاری پشتیبان کانتینر'
ContainerBackupStop: 'توقف کانتینر قبل از پشتیبان‌گیری'
ContainerBackupInspect: 'بررسی و شبکه کانتینر پشتیبان'
@@ -719,6 +722,8 @@ ErrDockerIPv4ForwardingDisabled: "ارسال IPv4 غیرفعال است. پیش
ErrFirewallRuleSavedApplyFailed: "پیکربندی قانون جدید ذخیره شد، اما اعمال آن در دیوار آتش ناموفق بود. با همگام‌سازی دوباره تلاش کنید: {{ .detail }}"
ErrFirewallRuleConflict: "یک قانون UFW با شرایط تطبیق یکسان و عملکرد متفاوت از قبل وجود دارد. قانون موجود را ویرایش کنید."
ErrForwardInterfaceNotFound: "رابط شبکه {{ .name }} مشخص‌شده در قانون انتقال وجود ندارد. نام رابط را بررسی کرده و دوباره تلاش کنید."
ErrIPv6RARisk: "فعال‌سازی هدایت IPv6 به دلیل خطر قطع شبکه مسدود شد. مستندات و گزارش‌های سیستم را بررسی کنید."
ErrIPv6RACheckFailed: "فعال‌سازی هدایت IPv6 به دلیل عدم امکان بررسی پیکربندی شبکه مسدود شد. جزئیات در گزارش‌های سیستم موجود است."
ErrSSHPublicKey: "یک کلید عمومی SSH معتبر وارد کنید"
ErrSSHPrivateKey: "کلید خصوصی SSH نامعتبر یا عبارت عبور نادرست است؛ کلیدهای رمزگذاری‌شده به عبارت عبور اصلی نیاز دارند"
+5
View File
@@ -300,6 +300,9 @@ BuildCache: 'ビルドキャッシュ'
Volume: 'ストレージボリューム'
Network: 'ネットワーク'
PruneStart: 'クリーンアップ中、しばらくお待ちください...'
NetworkCleanupConnected: "ネットワーク [{{ .name }}] ({{ .id }}) をスキップしました:コンテナが接続されているか使用中のため、削除しませんでした"
NetworkCleanupSummary: "ネットワークのクリーンアップが完了しました:削除 {{ .deleted }} 件、スキップ {{ .skipped }} 件、失敗 {{ .failed }} 件"
NetworkCleanupPartialFailure: "一部のネットワークをクリーンアップできませんでした。タスクログを確認してください"
ContainerBackupPrepare: 'コンテナバックアップ作業領域を準備'
ContainerBackupStop: 'バックアップ前にコンテナを停止'
ContainerBackupInspect: 'コンテナ inspect とネットワーク情報をバックアップ'
@@ -718,6 +721,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 転送が無効です。Docker のファ
ErrFirewallRuleSavedApplyFailed: "新しいルール設定は保存されましたが、ファイアウォールへの適用に失敗しました。同期で再試行してください:{{ .detail }}"
ErrFirewallRuleConflict: "UFW に同じ一致条件でアクションが異なるルールが既に存在します。既存のルールを編集してください。"
ErrForwardInterfaceNotFound: "転送ルールで指定されたネットワークインターフェース {{ .name }} は存在しません。名前を確認して再試行してください。"
ErrIPv6RARisk: "ネットワーク切断のおそれがあるため、IPv6 転送の有効化をブロックしました。ドキュメントとシステムログをご確認ください。"
ErrIPv6RACheckFailed: "ネットワーク設定を確認できないため、IPv6 転送の有効化をブロックしました。詳細はシステムログをご確認ください。"
ErrSSHPublicKey: "有効な SSH 公開鍵を入力してください"
ErrSSHPrivateKey: "SSH 秘密鍵の形式が無効か、パスフレーズが間違っています。暗号化された鍵には元のパスフレーズが必要です"
+5
View File
@@ -300,6 +300,9 @@ BuildCache: '빌드 캐시'
Volume: '스토리지 볼륨'
Network: '네트워크'
PruneStart: '정리 중입니다. 잠시만 기다려주세요...'
NetworkCleanupConnected: "네트워크 [{{ .name }}] ({{ .id }}) 건너뜀: 연결된 컨테이너가 있거나 사용 중이므로 삭제하지 않음"
NetworkCleanupSummary: "네트워크 정리 완료: 삭제 {{ .deleted }}개, 건너뜀 {{ .skipped }}개, 실패 {{ .failed }}개"
NetworkCleanupPartialFailure: "일부 네트워크를 정리하지 못했습니다. 작업 로그를 확인하세요"
ContainerBackupPrepare: '컨테이너 백업 작업 공간 준비'
ContainerBackupStop: '백업 전에 컨테이너 중지'
ContainerBackupInspect: '컨테이너 inspect 및 네트워크 정보 백업'
@@ -718,6 +721,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 전달이 비활성화되어 있습니다
ErrFirewallRuleSavedApplyFailed: "새 규칙 설정이 저장되었지만 방화벽에 적용하지 못했습니다. 동기화하여 다시 시도하세요: {{ .detail }}"
ErrFirewallRuleConflict: "UFW에 일치 조건이 같지만 동작이 다른 규칙이 이미 있습니다. 기존 규칙을 편집하세요."
ErrForwardInterfaceNotFound: "전달 규칙에 지정된 네트워크 인터페이스 {{ .name }}이 존재하지 않습니다. 이름을 확인한 후 다시 시도하세요."
ErrIPv6RARisk: "네트워크 연결이 끊길 위험이 있어 IPv6 전달 활성화를 차단했습니다. 문서와 시스템 로그를 확인하세요."
ErrIPv6RACheckFailed: "네트워크 구성을 확인할 수 없어 IPv6 전달 활성화를 차단했습니다. 자세한 내용은 시스템 로그를 확인하세요."
ErrSSHPublicKey: "유효한 SSH 공개 키를 입력하세요"
ErrSSHPrivateKey: "SSH 개인 키 형식이 잘못되었거나 암호가 틀립니다. 암호화된 키에는 원래 암호가 필요합니다"
+5
View File
@@ -290,6 +290,9 @@ BuildCache: 'ແຄດການສ້າງ'
Volume: 'ວໍລຸມ (Volume)'
Network: "ເຄືອຂ່າຍ"
PruneStart: 'ກຳລັງດຳເນີນການທຳຄວາມສະອາດ'
NetworkCleanupConnected: "ຂ້າມເຄືອຂ່າຍ [{{ .name }}] ({{ .id }}): ມີຄອນເທນເນີເຊື່ອມຕໍ່ຢູ່ ຫຼື ເຄືອຂ່າຍກຳລັງຖືກໃຊ້ງານ; ບໍ່ໄດ້ລຶບ"
NetworkCleanupSummary: "ການລ້າງເຄືອຂ່າຍສຳເລັດ: ລຶບແລ້ວ {{ .deleted }}, ຂ້າມ {{ .skipped }}, ລົ້ມເຫຼວ {{ .failed }}"
NetworkCleanupPartialFailure: "ບໍ່ສາມາດລ້າງບາງເຄືອຂ່າຍໄດ້; ກະລຸນາເບິ່ງບັນທຶກວຽກ"
ContainerBackupPrepare: 'ກຽມພື້ນທີ່ເຮັດວຽກສຳລັບສຳຮອງຂໍ້ມູນຄອນເທນເນີ'
ContainerBackupStop: 'ຢຸດຄອນເທນເນີກ່ອນການສຳຮອງຂໍ້ມູນ'
ContainerBackupInspect: 'ກວດສອບຄອນເທນເນີ ແລະ ເຄືອຂ່າຍສຳລັບການສຳຮອງຂໍ້ມູນ'
@@ -709,6 +712,8 @@ ErrDockerIPv4ForwardingDisabled: "ການສົ່ງຕໍ່ IPv4 ຖືກ
ErrFirewallRuleSavedApplyFailed: "ບັນທຶກການຕັ້ງຄ່າກົດໃໝ່ແລ້ວ ແຕ່ນຳໃຊ້ກັບໄຟວໍບໍ່ສຳເລັດ. ລອງອີກຄັ້ງດ້ວຍການຊິງຂໍ້ມູນ: {{ .detail }}"
ErrFirewallRuleConflict: "ມີກົດ UFW ທີ່ມີເງື່ອນໄຂກົງກັນແຕ່ການດຳເນີນການຕ່າງກັນແລ້ວ. ກະລຸນາແກ້ໄຂກົດທີ່ມີຢູ່."
ErrForwardInterfaceNotFound: "ບໍ່ພົບອິນເຕີເຟດເຄືອຂ່າຍ {{ .name }} ທີ່ລະບຸໃນກົດສົ່ງຕໍ່. ກະລຸນາກວດສອບຊື່ ແລ້ວລອງໃໝ່."
ErrIPv6RARisk: "ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກເພື່ອປ້ອງກັນຄວາມສ່ຽງທີ່ເຄືອຂ່າຍຈະຂາດການເຊື່ອມຕໍ່. ເບິ່ງເອກະສານ ແລະ ບັນທຶກລະບົບ."
ErrIPv6RACheckFailed: "ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກເນື່ອງຈາກບໍ່ສາມາດກວດສອບການຕັ້ງຄ່າເຄືອຂ່າຍໄດ້. ເບິ່ງລາຍລະອຽດໃນບັນທຶກລະບົບ."
ErrSSHPublicKey: "ກະລຸນາປ້ອນຄີສາທາລະນະ SSH ທີ່ຖືກຕ້ອງ"
ErrSSHPrivateKey: "ຄີສ່ວນຕົວ SSH ບໍ່ຖືກຕ້ອງ ຫຼື ລະຫັດຜ່ານຜິດ; ຄີທີ່ເຂົ້າລະຫັດຕ້ອງໃຊ້ລະຫັດຜ່ານເດີມ"
+5
View File
@@ -300,6 +300,9 @@ BuildCache: 'Cache binaan'
Volume: 'Jilid storan'
Network: 'Rangkaian'
PruneStart: 'Pembersihan sedang dijalankan, sila tunggu...'
NetworkCleanupConnected: "Rangkaian [{{ .name }}] ({{ .id }}) dilangkau: terdapat kontena yang disambungkan atau rangkaian sedang digunakan; tidak dipadamkan"
NetworkCleanupSummary: "Pembersihan rangkaian selesai: dipadamkan {{ .deleted }}, dilangkau {{ .skipped }}, gagal {{ .failed }}"
NetworkCleanupPartialFailure: "Sesetengah rangkaian tidak dapat dibersihkan; semak log tugas"
ContainerBackupPrepare: 'Sediakan ruang kerja sandaran kontena'
ContainerBackupStop: 'Hentikan kontena sebelum sandaran'
ContainerBackupInspect: 'Sandarkan inspect dan rangkaian kontena'
@@ -718,6 +721,8 @@ ErrDockerIPv4ForwardingDisabled: "Pemajuan IPv4 dilumpuhkan. Tetapkan net.ipv4.i
ErrFirewallRuleSavedApplyFailed: "Konfigurasi peraturan baharu telah disimpan, tetapi gagal digunakan pada tembok api. Cuba lagi melalui penyegerakan: {{ .detail }}"
ErrFirewallRuleConflict: "Peraturan UFW dengan syarat padanan yang sama tetapi tindakan berbeza sudah wujud. Sila edit peraturan sedia ada."
ErrForwardInterfaceNotFound: "Antara muka rangkaian {{ .name }} yang dinyatakan dalam peraturan pemajuan tidak wujud. Semak namanya dan cuba lagi."
ErrIPv6RARisk: "Pemajuan IPv6 disekat bagi mengelakkan risiko kehilangan sambungan rangkaian. Rujuk dokumentasi dan log sistem."
ErrIPv6RACheckFailed: "Pemajuan IPv6 disekat kerana konfigurasi rangkaian tidak dapat diperiksa. Lihat log sistem untuk butiran."
ErrSSHPublicKey: "Masukkan kunci awam SSH yang sah"
ErrSSHPrivateKey: "Kunci peribadi SSH tidak sah atau frasa laluan salah; kunci yang disulitkan memerlukan frasa laluan asal"
+5
View File
@@ -300,6 +300,9 @@ BuildCache: 'Cache de construção'
Volume: 'Volume de armazenamento'
Network: 'Rede'
PruneStart: 'Limpando em andamento aguarde...'
NetworkCleanupConnected: "Rede [{{ .name }}] ({{ .id }}) ignorada: há contêineres conectados ou a rede está em uso; não foi excluída"
NetworkCleanupSummary: "Limpeza de redes concluída: excluídas {{ .deleted }}, ignoradas {{ .skipped }}, falhas {{ .failed }}"
NetworkCleanupPartialFailure: "Não foi possível limpar algumas redes; consulte o log da tarefa"
ContainerBackupPrepare: 'Preparar área de backup do contêiner'
ContainerBackupStop: 'Parar contêiner antes do backup'
ContainerBackupInspect: 'Fazer backup do inspect e rede do contêiner'
@@ -718,6 +721,8 @@ ErrDockerIPv4ForwardingDisabled: "O encaminhamento IPv4 está desativado. Defina
ErrFirewallRuleSavedApplyFailed: "A configuração da nova regra foi salva, mas não pôde ser aplicada ao firewall. Tente novamente por meio da sincronização: {{ .detail }}"
ErrFirewallRuleConflict: "Já existe uma regra UFW com as mesmas condições e uma ação diferente. Edite a regra existente."
ErrForwardInterfaceNotFound: "A interface de rede {{ .name }} especificada na regra de encaminhamento não existe. Verifique o nome e tente novamente."
ErrIPv6RARisk: "O encaminhamento IPv6 foi bloqueado para evitar uma possível perda de conexão. Consulte a documentação e os logs do sistema."
ErrIPv6RACheckFailed: "O encaminhamento IPv6 foi bloqueado porque não foi possível verificar a configuração de rede. Consulte os logs do sistema."
ErrSSHPublicKey: "Informe uma chave pública SSH válida"
ErrSSHPrivateKey: "Chave privada SSH inválida ou senha incorreta; chaves criptografadas exigem a senha original"
+5
View File
@@ -300,6 +300,9 @@ BuildCache: 'Кэш сборки'
Volume: 'Том хранилища'
Network: 'Сеть'
PruneStart: 'Очистка выполняется, пожалуйста, подождите...'
NetworkCleanupConnected: "Сеть [{{ .name }}] ({{ .id }}) пропущена: подключены контейнеры или сеть используется; не удалена"
NetworkCleanupSummary: "Очистка сетей завершена: удалено {{ .deleted }}, пропущено {{ .skipped }}, ошибок {{ .failed }}"
NetworkCleanupPartialFailure: "Не удалось очистить некоторые сети; проверьте журнал задачи"
ContainerBackupPrepare: 'Подготовить рабочую область резервной копии контейнера'
ContainerBackupStop: 'Остановить контейнер перед резервным копированием'
ContainerBackupInspect: 'Сделать резервную копию inspect и сети контейнера'
@@ -718,6 +721,8 @@ ErrDockerIPv4ForwardingDisabled: "Пересылка IPv4 отключена. П
ErrFirewallRuleSavedApplyFailed: "Настройки нового правила сохранены, но применить их к межсетевому экрану не удалось. Повторите попытку с помощью синхронизации: {{ .detail }}"
ErrFirewallRuleConflict: "В UFW уже есть правило с такими же условиями и другим действием. Отредактируйте существующее правило."
ErrForwardInterfaceNotFound: "Сетевой интерфейс {{ .name }}, указанный в правиле перенаправления, не существует. Проверьте имя и повторите попытку."
ErrIPv6RARisk: "Включение переадресации IPv6 заблокировано из-за риска потери связи. См. документацию и системные журналы."
ErrIPv6RACheckFailed: "Включение переадресации IPv6 заблокировано: не удалось проверить настройки сети. Подробности в системных журналах."
ErrSSHPublicKey: "Введите действительный открытый ключ SSH"
ErrSSHPrivateKey: "Недопустимый закрытый ключ SSH или неверная парольная фраза; для зашифрованных ключей требуется исходная парольная фраза"
+5
View File
@@ -300,6 +300,9 @@ BuildCache: 'Derleme önbelleği'
Volume: 'Depolama hacmi'
Network: 'Ağ'
PruneStart: 'Temizlik devam ediyor, lütfen bekleyin...'
NetworkCleanupConnected: "Ağ [{{ .name }}] ({{ .id }}) atlandı: bağlı konteynerler var veya ağ kullanımda; silinmedi"
NetworkCleanupSummary: "Ağ temizliği tamamlandı: silinen {{ .deleted }}, atlanan {{ .skipped }}, başarısız {{ .failed }}"
NetworkCleanupPartialFailure: "Bazı ağlar temizlenemedi; görev günlüğünü kontrol edin"
ContainerBackupPrepare: 'Konteyner yedekleme çalışma alanını hazırla'
ContainerBackupStop: 'Yedeklemeden önce konteyneri durdur'
ContainerBackupInspect: 'Konteyner inspect ve ağ bilgisini yedekle'
@@ -718,6 +721,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 yönlendirmesi devre dışı. Docker güv
ErrFirewallRuleSavedApplyFailed: "Yeni kural yapılandırması kaydedildi, ancak güvenlik duvarına uygulanamadı. Eşitleme yaparak yeniden deneyin: {{ .detail }}"
ErrFirewallRuleConflict: "UFW içinde aynı eşleşme koşullarına sahip, farklı bir eylem uygulayan bir kural zaten var. Mevcut kuralı düzenleyin."
ErrForwardInterfaceNotFound: "Yönlendirme kuralında belirtilen {{ .name }} ağ arayüzü mevcut değil. Arayüz adını kontrol edip yeniden deneyin."
ErrIPv6RARisk: "Ağ bağlantısının kesilme riski nedeniyle IPv6 yönlendirmesi engellendi. Belgeleri ve sistem günlüklerini inceleyin."
ErrIPv6RACheckFailed: "Ağ yapılandırması denetlenemediği için IPv6 yönlendirmesi engellendi. Ayrıntılar için sistem günlüklerini inceleyin."
ErrSSHPublicKey: "Geçerli bir SSH açık anahtarı girin"
ErrSSHPrivateKey: "SSH özel anahtarı geçersiz veya parola yanlış; şifreli anahtarlar mevcut parolayı gerektirir"
+5
View File
@@ -300,6 +300,9 @@ BuildCache: '構建快取'
Volume: '磁碟區'
Network: '網路'
PruneStart: '清理中,請稍候...'
NetworkCleanupConnected: "跳過網路 [{{ .name }}] ({{ .id }}):仍有容器連接或正在使用,未刪除"
NetworkCleanupSummary: "網路清理完成:已刪除 {{ .deleted }},已跳過 {{ .skipped }},失敗 {{ .failed }}"
NetworkCleanupPartialFailure: "部分網路清理失敗,請查看任務日誌"
ContainerBackupPrepare: '準備容器備份工作目錄'
ContainerBackupStop: '備份前停止容器'
ContainerBackupInspect: '備份容器 Inspect 與網路資訊'
@@ -718,6 +721,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 轉送尚未啟用,請先設定 net.ipv
ErrFirewallRuleSavedApplyFailed: "新規則設定已儲存,但套用至防火牆失敗。可透過同步重試:{{ .detail }}"
ErrFirewallRuleConflict: "UFW 中已存在比對條件相同但動作不同的規則,請編輯既有規則。"
ErrForwardInterfaceNotFound: "轉發規則指定的網卡 {{ .name }} 不存在,請檢查網卡名稱後重試。"
ErrIPv6RARisk: "IPv6 轉送可能導致網路中斷,已阻止啟用。請參閱文件,詳情見系統日誌。"
ErrIPv6RACheckFailed: "無法檢查 IPv6 網路設定,已阻止啟用轉送。詳情見系統日誌。"
ErrSSHPublicKey: "請輸入有效的 SSH 公鑰"
ErrSSHPrivateKey: "SSH 私鑰格式無效或密碼錯誤;加密私鑰需要填寫原密碼"
+5
View File
@@ -300,6 +300,9 @@ BuildCache: "构建缓存"
Volume: "存储卷"
Network: "网络"
PruneStart: "清理中,请稍候..."
NetworkCleanupConnected: "跳过网络 [{{ .name }}] ({{ .id }}):仍有容器连接或正在使用,未删除"
NetworkCleanupSummary: "网络清理完成:已删除 {{ .deleted }},已跳过 {{ .skipped }},失败 {{ .failed }}"
NetworkCleanupPartialFailure: "部分网络清理失败,请查看任务日志"
ContainerBackupPrepare: "准备容器备份工作目录"
ContainerBackupStop: "备份前停止容器"
ContainerBackupInspect: "备份容器 Inspect 与网络信息"
@@ -718,6 +721,8 @@ ErrDockerIPv4ForwardingDisabled: "IPv4 转发未开启,请先设置 net.ipv4.i
ErrFirewallRuleSavedApplyFailed: "新规则配置已保存,但应用到防火墙失败。可通过同步重试:{{ .detail }}"
ErrFirewallRuleConflict: "UFW 中已存在匹配条件相同但动作不同的规则,请编辑已有规则。"
ErrForwardInterfaceNotFound: "转发规则指定的网卡 {{ .name }} 不存在,请检查网卡名称后重试。"
ErrIPv6RARisk: "IPv6 转发存在断网风险,已阻止启用。请参阅文档,详情见系统日志。"
ErrIPv6RACheckFailed: "无法检查 IPv6 网络配置,已阻止启用转发。详情见系统日志。"
ErrSSHPublicKey: "请输入有效的 SSH 公钥"
ErrSSHPrivateKey: "SSH 私钥格式无效或密码错误;加密私钥需要填写原密码"
+13
View File
@@ -251,6 +251,10 @@ func (c *CommandHelper) buildPipeCommands(ctx context.Context, commands []PipeCo
cmdItem.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
cmdItem.Cancel = func() error {
_ = syscall.Kill(-cmdItem.Process.Pid, syscall.SIGKILL)
return cmdItem.Process.Kill()
}
cmds = append(cmds, cmdItem)
}
return cmds
@@ -335,6 +339,12 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
if newContext != nil {
cmd.Cancel = func() error {
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
return cmd.Process.Kill()
}
}
customWriter := &CustomWriter{taskItem: c.taskItem}
var stdout, stderr bytes.Buffer
@@ -398,6 +408,9 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
}()
select {
case err := <-done:
if newContext != nil && newContext.Err() != nil {
return "", c.pipeResultErr(newContext, err)
}
if err != nil {
return handleErr(&stdout, &stderr, c.IgnoreExist1, err)
}
+111 -14
View File
@@ -1,6 +1,8 @@
package forwarding
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
@@ -8,19 +10,27 @@ import (
"sort"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/docker/docker/api/types/network"
)
func ensureFamilyForwardingSysctls(system forwardingSystem, family string) error {
withIPv6 := family == FamilyIPv6
if withIPv6 {
interfaces, err := IPv6RAInterfaces(system.ReadFile)
interfaces, err := IPv6RAInterfaces(system.ReadFile, LoadDockerIPv4BridgePorts)
if err != nil {
return fmt.Errorf("check IPv6 Router Advertisement: %w", err)
global.LOG.Warnf("IPv6 forwarding blocked: failed to check Router Advertisement: %v", err)
return buserr.New("ErrIPv6RACheckFailed")
}
if len(interfaces) > 0 {
return fmt.Errorf("IPv6 forwarding blocked: interfaces %s may depend on RA/SLAAC with accept_ra=1; persist accept_ra=2 on interfaces that require RA before retrying", strings.Join(interfaces, ", "))
global.LOG.Warnf("IPv6 forwarding blocked: interfaces %s may depend on RA/SLAAC with accept_ra=1; review the network configuration and persist accept_ra=2 on interfaces that require RA before retrying", strings.Join(interfaces, ", "))
return buserr.New("ErrIPv6RARisk")
}
}
paths := []string{"/proc/sys/net/ipv4/ip_forward"}
@@ -43,7 +53,7 @@ func ensureFamilyForwardingSysctls(system forwardingSystem, family string) error
return nil
}
func IPv6RAInterfaces(readFile func(string) ([]byte, error)) ([]string, error) {
func IPv6RAInterfaces(readFile func(string) ([]byte, error), loadDockerPorts func() (map[string]bool, error)) ([]string, error) {
addresses, err := readFile("/proc/net/if_inet6")
if err != nil {
return nil, err
@@ -67,21 +77,16 @@ func IPv6RAInterfaces(readFile func(string) ([]byte, error)) ([]string, error) {
if err != nil {
return nil, err
}
if _, exists := globalAddresses[name]; !exists {
globalAddresses[name] = false
}
if fields[3] == "00" {
globalAddresses[name] = true
if flags&permanentAddress == 0 {
raInterfaces[name] = true
}
globalAddresses[name] = globalAddresses[name] || fields[3] == "00"
if fields[3] == "00" && flags&permanentAddress == 0 {
raInterfaces[name] = true
}
}
routes, err := readFile("/proc/net/ipv6_route")
if err != nil {
return nil, err
}
const raRouteFlags = 0x00010000 | 0x00800000
const raRouteFlags = 0x00010000 | 0x00080000 | 0x00800000
for _, line := range strings.Split(string(routes), "\n") {
fields := strings.Fields(line)
if len(fields) == 0 {
@@ -98,12 +103,15 @@ func IPv6RAInterfaces(readFile func(string) ([]byte, error)) ([]string, error) {
raInterfaces[fields[9]] = true
}
}
uncertainInterfaces := make(map[string]bool)
for name, hasGlobalAddress := range globalAddresses {
if !hasGlobalAddress {
if !hasGlobalAddress && !raInterfaces[name] {
raInterfaces[name] = true
uncertainInterfaces[name] = true
}
}
var interfaces []string
checkDockerPorts := false
for name := range raInterfaces {
if name == "lo" {
continue
@@ -118,15 +126,104 @@ func IPv6RAInterfaces(readFile func(string) ([]byte, error)) ([]string, error) {
switch strings.TrimSpace(string(value)) {
case "1":
interfaces = append(interfaces, name)
checkDockerPorts = checkDockerPorts || uncertainInterfaces[name]
case "0", "2":
default:
return nil, fmt.Errorf("invalid accept_ra value for %s", name)
}
}
if checkDockerPorts {
ports, err := loadDockerPorts()
if err != nil {
global.LOG.Warnf("IPv6 forwarding RA check: cannot exclude Docker bridge ports: %v", err)
} else {
kept := interfaces[:0]
for _, name := range interfaces {
if !uncertainInterfaces[name] || !ports[name] {
kept = append(kept, name)
}
}
interfaces = kept
}
}
sort.Strings(interfaces)
return interfaces, nil
}
func LoadDockerIPv4BridgePorts() (map[string]bool, error) {
cli, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
defer cli.Close()
if !strings.HasPrefix(cli.DaemonHost(), "unix://") {
return nil, errors.New("cannot verify local bridge ports using a remote Docker daemon")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
info, err := cli.Info(ctx)
if err != nil {
return nil, err
}
for _, option := range info.SecurityOptions {
if option == "name=rootless" {
return nil, errors.New("cannot verify host bridge ports using a rootless Docker daemon")
}
}
networks, err := cli.NetworkList(ctx, network.ListOptions{})
if err != nil {
return nil, err
}
output, err := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(5*time.Second)).RunWithOptionalSudoAndStdout("ip", "-details", "-json", "link", "show")
if err != nil {
return nil, err
}
return dockerIPv4BridgePorts(networks, output)
}
func dockerIPv4BridgePorts(networks []network.Summary, linkJSON string) (map[string]bool, error) {
bridges := make(map[string]bool)
for _, item := range networks {
if item.Driver != "bridge" || item.EnableIPv6 || item.ConfigOnly {
continue
}
name := item.Options["com.docker.network.bridge.name"]
if name == "" {
if item.Options["com.docker.network.bridge.default_bridge"] == "true" {
name = "docker0"
} else if len(item.ID) >= 12 {
name = "br-" + item.ID[:12]
}
}
if name != "" {
bridges[name] = true
}
}
var links []struct {
Name string `json:"ifname"`
Master string `json:"master"`
LinkInfo struct {
Kind string `json:"info_kind"`
} `json:"linkinfo"`
}
if err := json.Unmarshal([]byte(linkJSON), &links); err != nil {
return nil, err
}
confirmedBridges := make(map[string]bool)
for _, link := range links {
if link.LinkInfo.Kind == "bridge" && bridges[link.Name] {
confirmedBridges[link.Name] = true
}
}
ports := make(map[string]bool)
for _, link := range links {
if link.LinkInfo.Kind == "veth" && confirmedBridges[link.Master] {
ports[link.Name] = true
}
}
return ports, nil
}
func enableFamilyForwardingSysctls(content, family string) string {
lines := strings.Split(strings.TrimRight(content, "\n"), "\n")
wanted := map[string]string{"net.ipv4.ip_forward": "net.ipv4.ip_forward = 1"}
+26 -4
View File
@@ -15092,9 +15092,26 @@ const docTemplate = `{
},
"/databases/redis/install/cli": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.RedisCliInstall"
}
}
],
"responses": {
"200": {
"description": "OK"
"description": "OK",
"schema": {
"type": "string"
}
}
},
"security": [
@@ -36372,9 +36389,6 @@ const docTemplate = `{
"bound": {
"type": "boolean"
},
"forwardPolicy": {
"type": "string"
},
"initialized": {
"type": "boolean"
},
@@ -40152,6 +40166,14 @@ const docTemplate = `{
],
"type": "object"
},
"dto.RedisCliInstall": {
"type": "object",
"properties": {
"taskID": {
"type": "string"
}
}
},
"dto.RedisConf": {
"properties": {
"containerName": {
+26 -4
View File
@@ -15088,9 +15088,26 @@
},
"/databases/redis/install/cli": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.RedisCliInstall"
}
}
],
"responses": {
"200": {
"description": "OK"
"description": "OK",
"schema": {
"type": "string"
}
}
},
"security": [
@@ -36368,9 +36385,6 @@
"bound": {
"type": "boolean"
},
"forwardPolicy": {
"type": "string"
},
"initialized": {
"type": "boolean"
},
@@ -40148,6 +40162,14 @@
],
"type": "object"
},
"dto.RedisCliInstall": {
"type": "object",
"properties": {
"taskID": {
"type": "string"
}
}
},
"dto.RedisConf": {
"properties": {
"containerName": {
+15 -4
View File
@@ -230,6 +230,10 @@ func (c *CommandHelper) buildPipeCommands(ctx context.Context, commands []PipeCo
cmdItem.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
cmdItem.Cancel = func() error {
_ = syscall.Kill(-cmdItem.Process.Pid, syscall.SIGKILL)
return cmdItem.Process.Kill()
}
cmds = append(cmds, cmdItem)
}
return cmds
@@ -305,6 +309,12 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
if newContext != nil {
cmd.Cancel = func() error {
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
return cmd.Process.Kill()
}
}
customWriter := &CustomWriter{taskItem: c.taskItem}
var stdout, stderr bytes.Buffer
@@ -357,11 +367,12 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
}()
select {
case err := <-done:
if c.preserveErrorCause && newContext != nil && newContext.Err() != nil {
if cmd.Process != nil && cmd.Process.Pid > 0 {
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
if newContext != nil && newContext.Err() != nil {
resultErr := c.pipeResultErr(newContext, err)
if c.preserveErrorCause {
resultErr = &commandError{message: resultErr.Error(), cause: newContext.Err()}
}
return "", newContext.Err()
return "", resultErr
}
if err != nil {
out, resultErr := handleErr(&stdout, &stderr, c.IgnoreExist1, err)
-1
View File
@@ -23,7 +23,6 @@ export namespace Firewall {
initialized: boolean;
bound: boolean;
reason?: string;
forwardPolicy?: 'ACCEPT' | 'DROP';
raInterfaces?: string[];
}
export interface BackendGroup {
+4 -2
View File
@@ -193,8 +193,10 @@ export const redisPersistenceConf = (type: string, database: string) => {
export const checkRedisCli = () => {
return http.get<boolean>(`/databases/redis/check`);
};
export const installRedisCli = () => {
return http.post(`/databases/redis/install/cli`, {}, TimeoutEnum.T_5M);
export const installRedisCli = (taskID: string, node: string) => {
return http.post<string>(`/databases/redis/install/cli?operateNode=${encodeURIComponent(node)}`, {
taskID,
});
};
export const changeRedisPassword = (database: string, password: string) => {
if (password) {
@@ -15,7 +15,7 @@
</template>
<script setup lang="ts">
import { onActivated, ref, watch } from 'vue';
import { ref, watch } from 'vue';
defineOptions({ name: 'TableSearch' });
const emit = defineEmits(['search', 'update:searchName']);
@@ -42,10 +42,6 @@ watch(
{ immediate: true },
);
onActivated(() => {
searchInfo.value = props.searchName;
});
const search = () => {
emit('update:searchName', searchInfo.value);
emit('search');
@@ -167,7 +167,7 @@
</div>
</template>
<script setup lang="ts">
import { computed, onDeactivated, onMounted, ref, useAttrs } from 'vue';
import { computed, onMounted, ref, useAttrs } from 'vue';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { flattenVNodes, type FuTableOperationButton } from '@/components/table/shared';
import { useCardColumns } from './useCardColumns';
@@ -459,8 +459,6 @@ watch([currentViewMode, tableData, () => props.syncCardContentHeight], scheduleC
flush: 'post',
});
onDeactivated(clearSelects);
watch([() => props.paginationConfig?.currentPage, () => props.paginationConfig?.pageSize], clearSelects, {
flush: 'sync',
});
+7 -2
View File
@@ -1,6 +1,11 @@
import { reactive, type UnwrapNestedRefs } from 'vue';
import { useRoute } from 'vue-router';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { getPageState } from '@/utils/page-state-cache';
/** A new page visit starts from its defaults instead of restoring the previous page number. */
export const usePageState = <T extends object>(factory: () => T): UnwrapNestedRefs<T> => {
return reactive(factory());
const route = useRoute();
const { currentNode } = useGlobalStore();
const key = `${currentNode.value}:${String(route.name || route.path)}`;
return reactive(getPageState(key, factory));
};
@@ -0,0 +1,63 @@
import { storeToRefs } from 'pinia';
import { watch, type Ref } from 'vue';
import GlobalStore from '@/store/modules/global';
type SearchValue = string | number | undefined;
type SearchFields = Record<string, Ref<SearchValue>>;
export const useSearchPersistence = (page: string, fields: SearchFields, scope?: () => SearchValue) => {
const { currentNode } = storeToRefs(GlobalStore());
const defaults = Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value]));
let initialized = false;
let restoring = false;
let activeKey: string | undefined;
watch(
() => {
const context = scope?.();
if (scope && context === undefined) return undefined;
return `1panel:search:v1:${JSON.stringify([currentNode.value, page, context ?? ''])}`;
},
(key) => {
activeKey = key;
let saved: Record<string, unknown> = {};
try {
const parsed = activeKey ? JSON.parse(localStorage.getItem(activeKey) || '{}') : {};
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) saved = parsed;
} catch {}
restoring = true;
for (const [name, field] of Object.entries(fields)) {
const value = saved[name];
const initial = defaults[name];
let fallback = initial;
if (initialized) {
fallback = typeof initial === 'string' ? '' : undefined;
}
const valid =
(typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value))) &&
(fallback === undefined || typeof value === typeof fallback);
if (value === null) {
field.value = typeof initial === 'string' ? '' : undefined;
} else if (valid) {
field.value = value as SearchValue;
} else {
field.value = fallback;
}
}
restoring = false;
initialized = true;
},
{ immediate: true, flush: 'sync' },
);
watch(
() => Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value ?? null])),
(value) => {
if (restoring || !activeKey) return;
try {
localStorage.setItem(activeKey, JSON.stringify(value));
} catch {}
},
{ flush: 'sync' },
);
};
@@ -1,24 +0,0 @@
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();
});
};
+4 -4
View File
@@ -570,6 +570,8 @@ const message = {
cancelKeepAlive: 'Stop keeping page session',
},
header: {
userCenter: 'User Center',
exitSystem: 'Log Out',
logout: 'Logout',
},
database: {
@@ -4306,8 +4308,6 @@ const message = {
dockerGuard: 'Container Port Guard',
systemFirewall: 'Host firewall',
systemFirewallHelper: 'Controls host port access and inbound firewall rules.',
forwardPolicyDropWarning:
'This server restricts network forwarding. Port forwarding rules may not take effect.',
forwardingHelper: 'Manages port-forwarding rules.',
dockerFirewallHelper: 'Selects how 1Panel manages container port protection.',
dockerNftablesRequirement: 'Docker ≥ 29.0.0, experimental',
@@ -4390,9 +4390,9 @@ const message = {
'Host INPUT rules do not directly protect this Docker published port. Click to open Container Port Guard.',
notInitialized: 'Not initialized',
ipv6RARisk:
'IPv6 forwarding is blocked: interfaces {0} have accept_ra=1 and may rely on RA/SLAAC. Review the network configuration, persist accept_ra=2 on interfaces that need RA, then retry.',
'IPv6 forwarding is blocked to prevent possible network loss. See the documentation and system logs for details.',
ipv6RACheckFailed:
'Cannot check IPv6 RA configuration. Enabling IPv6 forwarding is blocked. Check the system network configuration and retry.',
'IPv6 forwarding is blocked because the network configuration could not be checked. See the system logs for details.',
ipv6ForwardingOnDemand:
'IPv6 forwarding is not enabled. It will be checked and enabled when IPv6 rules are added or restored.',
familyUnsupported: 'The system does not support {0}',
+4 -4
View File
@@ -570,6 +570,8 @@ const message = {
cancelKeepAlive: 'Dejar de mantener la sesión de la página',
},
header: {
userCenter: 'Centro de usuario',
exitSystem: 'Cerrar sesión',
logout: 'Cerrar sesión',
},
database: {
@@ -4357,8 +4359,6 @@ const message = {
dockerGuard: 'Protección de puertos de contenedores',
systemFirewall: 'Firewall del host',
systemFirewallHelper: 'Controla el acceso a los puertos del host y las reglas de entrada.',
forwardPolicyDropWarning:
'Este servidor restringe el reenvío de red. Es posible que las reglas de reenvío de puertos no surtan efecto.',
forwardingHelper: 'Gestiona las reglas de reenvío de puertos.',
dockerFirewallHelper: 'Selecciona cómo gestiona 1Panel la protección de puertos de contenedores.',
dockerNftablesRequirement: 'Docker ≥ 29.0.0, experimental',
@@ -4443,9 +4443,9 @@ const message = {
'Las reglas INPUT del host no protegen directamente este puerto publicado por Docker. Haz clic para abrir la protección de puertos de contenedores.',
notInitialized: 'No inicializado',
ipv6RARisk:
'El reenvío IPv6 está bloqueado: las interfaces {0} tienen accept_ra=1 y pueden depender de RA/SLAAC. Revise la configuración de red, configure accept_ra=2 de forma persistente en las interfaces que necesitan RA y vuelva a intentarlo.',
'El reenvío IPv6 está bloqueado para evitar una posible pérdida de conectividad. Consulte la documentación y los registros del sistema.',
ipv6RACheckFailed:
'No se puede comprobar la configuración RA de IPv6. Se ha bloqueado la activación del reenvío IPv6. Revise la configuración de red y vuelva a intentarlo.',
'El reenvío IPv6 está bloqueado porque no se pudo comprobar la configuración de red. Consulte los registros del sistema.',
ipv6ForwardingOnDemand:
'El reenvío IPv6 no está habilitado. Se comprobará y habilitará al añadir o restaurar reglas IPv6.',
familyUnsupported: 'El sistema no admite {0}',
+4 -4
View File
@@ -565,6 +565,8 @@ const message = {
cancelKeepAlive: 'لغو حفظ نشست صفحه',
},
header: {
userCenter: 'مرکز کاربری',
exitSystem: 'خروج از سیستم',
logout: 'خروج',
},
database: {
@@ -4271,7 +4273,6 @@ const message = {
dockerGuard: 'محافظت از پورت کانتینر',
systemFirewall: 'فایروال میزبان',
systemFirewallHelper: 'دسترسی پورت‌های میزبان و قوانین ورودی را کنترل می‌کند.',
forwardPolicyDropWarning: 'این سرور هدایت ترافیک شبکه را محدود کرده است. ممکن است قوانین هدایت پورت کار نکنند.',
forwardingHelper: 'قوانین انتقال پورت را مدیریت می‌کند.',
dockerFirewallHelper: 'روش مدیریت محافظت از پورت کانتینر در 1Panel را انتخاب می‌کند.',
dockerNftablesRequirement: 'Docker ≥ 29.0.0، آزمایشی',
@@ -4355,10 +4356,9 @@ const message = {
dockerInputNotProtected:
'قوانین INPUT میزبان مستقیماً از این پورت منتشرشده Docker محافظت نمی‌کنند. برای باز کردن محافظت از پورت کانتینر کلیک کنید.',
notInitialized: 'راه‌اندازی نشده',
ipv6RARisk:
'هدایت IPv6 مسدود شد: رابط‌های {0} دارای accept_ra=1 هستند و ممکن است به RA/SLAAC وابسته باشند. تنظیمات شبکه را بررسی کنید، برای رابط‌های نیازمند RA مقدار accept_ra=2 را به‌صورت پایدار تنظیم کنید و دوباره تلاش کنید.',
ipv6RARisk: 'فعال‌سازی هدایت IPv6 به دلیل خطر قطع شبکه مسدود شد. مستندات و گزارش‌های سیستم را بررسی کنید.',
ipv6RACheckFailed:
'بررسی تنظیمات RA در IPv6 ممکن نیست. فعال‌سازی هدایت IPv6 مسدود شده است. تنظیمات شبکه سیستم را بررسی کرده و دوباره تلاش کنید.',
'فعال‌سازی هدایت IPv6 به دلیل عدم امکان بررسی پیکربندی شبکه مسدود شد. جزئیات در گزارش‌های سیستم موجود است.',
ipv6ForwardingOnDemand: 'هدایت IPv6 فعال نیست. هنگام افزودن یا بازیابی قوانین IPv6 بررسی و فعال خواهد شد.',
familyUnsupported: 'سیستم از {0} پشتیبانی نمی‌کند',
familyChainIssue: '{0} · زنجیره {1} · {2}',
+4 -4
View File
@@ -568,6 +568,8 @@ const message = {
cancelKeepAlive: 'ページセッションの保持を解除',
},
header: {
userCenter: 'ユーザーセンター',
exitSystem: 'ログアウト',
logout: 'ログアウト',
},
database: {
@@ -4294,8 +4296,6 @@ const message = {
dockerGuard: 'コンテナポート保護',
systemFirewall: 'ホストファイアウォール',
systemFirewallHelper: 'ホストのポートアクセスと受信ルールを管理します。',
forwardPolicyDropWarning:
'このサーバーではネットワーク転送が制限されているため、ポート転送ルールが機能しない可能性があります。',
forwardingHelper: 'ポート転送ルールを管理します。',
dockerFirewallHelper: '1Panel のコンテナポート保護の管理方法を選択します。',
dockerNftablesRequirement: 'Docker ≥ 29.0.0、実験的',
@@ -4377,9 +4377,9 @@ const message = {
'ホストの INPUT ルールでは、この Docker 公開ポートを直接保護できません。クリックしてコンテナポート保護を開きます。',
notInitialized: '未初期化',
ipv6RARisk:
'IPv6 転送がブロックされています。インターフェース {0} は accept_ra=1 で、RA/SLAAC に依存している可能性があります。ネットワーク設定を確認し、RA が必要なインターフェースで accept_ra=2 を永続的に設定してから再試行してください。',
'ネットワーク切断のおそれがあるため、IPv6 転送の有効化をブロックしました。ドキュメントとシステムログをご確認ください。',
ipv6RACheckFailed:
'IPv6 RA 設定を確認できないため、IPv6 転送の有効化をブロックしました。システムのネットワーク設定を確認して再試行してください。',
'ネットワーク設定を確認できないため、IPv6 転送の有効化をブロックしました。詳細はシステムログをご確認ください。',
ipv6ForwardingOnDemand: 'IPv6 転送は無効です。IPv6 ルールの追加または復元時に確認して有効化します。',
familyUnsupported: 'システムは {0} をサポートしていません',
familyChainIssue: '{0} · {1} チェーン · {2}',
+4 -3
View File
@@ -562,6 +562,8 @@ const message = {
cancelKeepAlive: '페이지 세션 유지 해제',
},
header: {
userCenter: '사용자 센터',
exitSystem: '로그아웃',
logout: '로그아웃',
},
database: {
@@ -4215,7 +4217,6 @@ const message = {
dockerGuard: '컨테이너 포트 보호',
systemFirewall: '호스트 방화벽',
systemFirewallHelper: '호스트 포트 접근과 인바운드 규칙을 관리합니다.',
forwardPolicyDropWarning: '현재 서버에서 네트워크 전달이 제한되어 포트 전달 규칙이 작동하지 않을 수 있습니다.',
forwardingHelper: '포트 포워딩 규칙을 관리합니다.',
dockerFirewallHelper: '1Panel 컨테이너 포트 보호 관리 방식을 선택합니다.',
dockerNftablesRequirement: 'Docker ≥ 29.0.0, 실험적',
@@ -4296,9 +4297,9 @@ const message = {
'호스트 INPUT 규칙은 이 Docker 게시 포트를 직접 보호하지 않습니다. 클릭하여 컨테이너 포트 보호를 여세요.',
notInitialized: '초기화되지 않음',
ipv6RARisk:
'IPv6 전달이 차단되었습니다. 인터페이스 {0}의 accept_ra=1 설정으로 인해 RA/SLAAC에 의존할 수 있습니다. 네트워크 설정을 확인하고 RA가 필요한 인터페이스에 accept_ra=2를 영구 설정한 후 다시 시도하세요.',
'네트워크 연결이 끊길 위험이 있어 IPv6 전달 활성화를 차단했습니다. 문서와 시스템 로그를 확인하세요.',
ipv6RACheckFailed:
'IPv6 RA 설정을 확인할 수 없어 IPv6 전달 활성화를 차단했습니다. 시스템 네트워크 설정을 확인한 후 다시 시도하세요.',
'네트워크 구성을 확인할 수 없어 IPv6 전달 활성화를 차단했습니다. 자세한 내용은 시스템 로그를 확인하세요.',
ipv6ForwardingOnDemand:
'IPv6 전달이 활성화되지 않았습니다. IPv6 규칙을 추가하거나 복원할 때 확인 후 활성화됩니다.',
familyUnsupported: '시스템에서 {0}을 지원하지 않습니다',
+4 -3
View File
@@ -563,6 +563,8 @@ const message = {
cancelKeepAlive: 'ຍົກເລີກການຮັກສາເຊັດຊັນໜ້າ',
},
header: {
userCenter: 'ສູນຜູ້ໃຊ້',
exitSystem: 'ອອກຈາກລະບົບ',
logout: 'ອອກຈາກລະບົບ',
},
database: {
@@ -4182,7 +4184,6 @@ const message = {
dockerGuard: 'ການປ້ອງກັນພອດຄອນເທນເນີ',
systemFirewall: 'ໄຟວໍໂຮສ',
systemFirewallHelper: 'ຄວບຄຸມການເຂົ້າເຖິງພອດໂຮສ ແລະ ກົດຂາເຂົ້າ.',
forwardPolicyDropWarning: 'ເຊີບເວີນີ້ຈຳກັດການສົ່ງຕໍ່ເຄືອຂ່າຍ. ກົດການສົ່ງຕໍ່ພອດອາດບໍ່ເຮັດວຽກ.',
forwardingHelper: 'ຈັດການກົດການສົ່ງຕໍ່ພອດ.',
dockerFirewallHelper: 'ເລືອກວິທີທີ່ 1Panel ຈັດການການປ້ອງກັນພອດຄອນເທນເນີ.',
dockerNftablesRequirement: 'Docker ≥ 29.0.0, ທົດລອງ',
@@ -4260,9 +4261,9 @@ const message = {
'ກົດ INPUT ຂອງໂຮສບໍ່ໄດ້ປ້ອງກັນພອດ Docker ນີ້ໂດຍກົງ. ຄລິກເພື່ອເປີດການປ້ອງກັນພອດຄອນເທນເນີ.',
notInitialized: 'ຍັງບໍ່ໄດ້ເລີ່ມຕົ້ນ',
ipv6RARisk:
'ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກ: ອິນເຕີເຟດ {0} ມີ accept_ra=1 ແລະອາດອາໄສ RA/SLAAC. ກວດສອບການຕັ້ງຄ່າເຄືອຂ່າຍ, ຕັ້ງ accept_ra=2 ແບບຖາວອນໃຫ້ອິນເຕີເຟດທີ່ຕ້ອງການ RA ແລ້ວລອງໃໝ່.',
'ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກເພື່ອປ້ອງກັນຄວາມສ່ຽງທີ່ເຄືອຂ່າຍຈະຂາດການເຊື່ອມຕໍ່. ເບິ່ງເອກະສານ ແລະ ບັນທຶກລະບົບ.',
ipv6RACheckFailed:
'ບໍ່ສາມາດກວດສອບການຕັ້ງຄ່າ IPv6 RA ໄດ້. ການເປີດໃຊ້ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກ. ກວດສອບການຕັ້ງຄ່າເຄືອຂ່າຍແລ້ວລອງໃໝ່.',
'ການສົ່ງຕໍ່ IPv6 ຖືກບລັອກເນື່ອງຈາກບໍ່ສາມາດກວດສອບການຕັ້ງຄ່າເຄືອຂ່າຍໄດ້. ເບິ່ງລາຍລະອຽດໃນບັນທຶກລະບົບ.',
ipv6ForwardingOnDemand: 'ຍັງບໍ່ໄດ້ເປີດໃຊ້ການສົ່ງຕໍ່ IPv6. ລະບົບຈະກວດສອບແລະເປີດໃຊ້ເມື່ອເພີ່ມຫຼືກູ້ຄືນກົດ IPv6.',
familyUnsupported: 'ລະບົບບໍ່ຮອງຮັບ {0}',
familyChainIssue: '{0} · chain {1} · {2}',
+4 -4
View File
@@ -575,6 +575,8 @@ const message = {
cancelKeepAlive: 'Hentikan pengekalan sesi halaman',
},
header: {
userCenter: 'Pusat Pengguna',
exitSystem: 'Log keluar',
logout: 'Log keluar',
},
database: {
@@ -4374,8 +4376,6 @@ const message = {
dockerGuard: 'Perlindungan port bekas',
systemFirewall: 'Tembok api hos',
systemFirewallHelper: 'Mengawal akses port hos dan peraturan masuk.',
forwardPolicyDropWarning:
'Pelayan ini mengehadkan pemajuan rangkaian. Peraturan pemajuan port mungkin tidak berfungsi.',
forwardingHelper: 'Mengurus peraturan pemajuan port.',
dockerFirewallHelper: 'Memilih cara 1Panel mengurus perlindungan port bekas.',
dockerNftablesRequirement: 'Docker ≥ 29.0.0, percubaan',
@@ -4459,9 +4459,9 @@ const message = {
'Peraturan INPUT hos tidak melindungi port terbitan Docker ini secara langsung. Klik untuk membuka perlindungan port bekas.',
notInitialized: 'Belum dimulakan',
ipv6RARisk:
'Pemajuan IPv6 disekat: antara muka {0} mempunyai accept_ra=1 dan mungkin bergantung pada RA/SLAAC. Semak konfigurasi rangkaian, tetapkan accept_ra=2 secara kekal pada antara muka yang memerlukan RA, kemudian cuba lagi.',
'Pemajuan IPv6 disekat bagi mengelakkan risiko kehilangan sambungan rangkaian. Rujuk dokumentasi dan log sistem.',
ipv6RACheckFailed:
'Konfigurasi RA IPv6 tidak dapat diperiksa. Pengaktifan pemajuan IPv6 disekat. Semak konfigurasi rangkaian sistem dan cuba lagi.',
'Pemajuan IPv6 disekat kerana konfigurasi rangkaian tidak dapat diperiksa. Lihat log sistem untuk butiran.',
ipv6ForwardingOnDemand:
'Pemajuan IPv6 belum diaktifkan. Ia akan diperiksa dan diaktifkan apabila peraturan IPv6 ditambah atau dipulihkan.',
familyUnsupported: 'Sistem tidak menyokong {0}',
+4 -4
View File
@@ -568,6 +568,8 @@ const message = {
cancelKeepAlive: 'Parar de manter a sessão da página',
},
header: {
userCenter: 'Central do usuário',
exitSystem: 'Sair',
logout: 'Logout',
},
database: {
@@ -4395,8 +4397,6 @@ const message = {
dockerGuard: 'Proteção de portas de contêineres',
systemFirewall: 'Firewall do host',
systemFirewallHelper: 'Controla o acesso às portas do host e as regras de entrada.',
forwardPolicyDropWarning:
'Este servidor restringe o encaminhamento de rede. As regras de encaminhamento de portas podem não funcionar.',
forwardingHelper: 'Gerencia regras de encaminhamento de portas.',
dockerFirewallHelper: 'Seleciona como o 1Panel gerencia a proteção de portas de contêineres.',
dockerNftablesRequirement: 'Docker ≥ 29.0.0, experimental',
@@ -4482,9 +4482,9 @@ const message = {
'As regras INPUT do host não protegem diretamente esta porta publicada pelo Docker. Clique para abrir a proteção de portas de contêineres.',
notInitialized: 'Não inicializado',
ipv6RARisk:
'O encaminhamento IPv6 foi bloqueado: as interfaces {0} têm accept_ra=1 e podem depender de RA/SLAAC. Verifique a configuração de rede, configure accept_ra=2 de forma persistente nas interfaces que precisam de RA e tente novamente.',
'O encaminhamento IPv6 foi bloqueado para evitar uma possível perda de conexão. Consulte a documentação e os logs do sistema.',
ipv6RACheckFailed:
'Não foi possível verificar a configuração RA do IPv6. A ativação do encaminhamento IPv6 foi bloqueada. Verifique a configuração de rede e tente novamente.',
'O encaminhamento IPv6 foi bloqueado porque não foi possível verificar a configuração de rede. Consulte os logs do sistema.',
ipv6ForwardingOnDemand:
'O encaminhamento IPv6 não está ativado. Ele será verificado e ativado ao adicionar ou restaurar regras IPv6.',
familyUnsupported: 'O sistema não oferece suporte a {0}',
+4 -4
View File
@@ -566,6 +566,8 @@ const message = {
cancelKeepAlive: 'Не сохранять сеанс страницы',
},
header: {
userCenter: 'Центр пользователя',
exitSystem: 'Выйти из системы',
logout: 'Выход',
},
database: {
@@ -4361,8 +4363,6 @@ const message = {
dockerGuard: 'Защита портов контейнеров',
systemFirewall: 'Брандмауэр хоста',
systemFirewallHelper: 'Управляет доступом к портам хоста и входящими правилами.',
forwardPolicyDropWarning:
'На этом сервере ограничена пересылка сетевого трафика. Правила перенаправления портов могут не работать.',
forwardingHelper: 'Управляет правилами перенаправления портов.',
dockerFirewallHelper: 'Выбирает способ управления защитой портов контейнеров в 1Panel.',
dockerNftablesRequirement: 'Docker ≥ 29.0.0, экспериментально',
@@ -4447,9 +4447,9 @@ const message = {
'Правила INPUT хоста не защищают этот опубликованный Docker-порт напрямую. Нажмите, чтобы открыть защиту портов контейнеров.',
notInitialized: 'Не инициализировано',
ipv6RARisk:
'Пересылка IPv6 заблокирована: интерфейсы {0} имеют accept_ra=1 и могут зависеть от RA/SLAAC. Проверьте настройки сети, сохраните accept_ra=2 для интерфейсов, которым нужен RA, и повторите попытку.',
'Включение переадресации IPv6 заблокировано из-за риска потери связи. См. документацию и системные журналы.',
ipv6RACheckFailed:
'Не удалось проверить настройки RA для IPv6. Включение пересылки IPv6 заблокировано. Проверьте настройки сети и повторите попытку.',
'Включение переадресации IPv6 заблокировано: не удалось проверить настройки сети. Подробности в системных журналах.',
ipv6ForwardingOnDemand:
'Пересылка IPv6 не включена. Проверка и включение выполняются при добавлении или восстановлении правил IPv6.',
familyUnsupported: 'Система не поддерживает {0}',
+4 -3
View File
@@ -570,6 +570,8 @@ const message = {
cancelKeepAlive: 'Sayfa oturumunu korumayı bırak',
},
header: {
userCenter: 'Kullanıcı Merkezi',
exitSystem: 'Çıkış yap',
logout: 'Çıkış',
},
database: {
@@ -4379,7 +4381,6 @@ const message = {
dockerGuard: 'Konteyner portu koruması',
systemFirewall: 'Ana makine güvenlik duvarı',
systemFirewallHelper: 'Ana makine port erişimini ve gelen kuralları yönetir.',
forwardPolicyDropWarning: 'Bu sunucu ağ yönlendirmesini kısıtlıyor. Port yönlendirme kuralları çalışmayabilir.',
forwardingHelper: 'Port yönlendirme kurallarını yönetir.',
dockerFirewallHelper: '1Panel konteyner portu korumasının nasıl yönetileceğini seçer.',
dockerNftablesRequirement: 'Docker ≥ 29.0.0, deneysel',
@@ -4466,9 +4467,9 @@ const message = {
'Ana makine INPUT kuralları bu Docker yayımlanmış portunu doğrudan korumaz. Konteyner portu korumasını açmak için tıklayın.',
notInitialized: 'Başlatılmadı',
ipv6RARisk:
'IPv6 yönlendirme engellendi: {0} arayüzlerinde accept_ra=1 ayarlı ve bu arayüzler RA/SLAAC kullanıyor olabilir. Ağ yapılandırmasını kontrol edin, RA gereken arayüzlerde accept_ra=2 ayarını kalıcı olarak uygulayın ve yeniden deneyin.',
'Ağ bağlantısının kesilme riski nedeniyle IPv6 yönlendirmesi engellendi. Belgeleri ve sistem günlüklerini inceleyin.',
ipv6RACheckFailed:
'IPv6 RA yapılandırması kontrol edilemedi. IPv6 yönlendirmeyi etkinleştirme engellendi. Sistem ağ yapılandırmasını kontrol edip yeniden deneyin.',
'Ağ yapılandırması denetlenemediği için IPv6 yönlendirmesi engellendi. Ayrıntılar için sistem günlüklerini inceleyin.',
ipv6ForwardingOnDemand:
'IPv6 yönlendirme etkin değil. IPv6 kuralları eklenirken veya geri yüklenirken kontrol edilip etkinleştirilecektir.',
familyUnsupported: 'Sistem {0} desteğini sunmuyor',
+4 -4
View File
@@ -548,6 +548,8 @@ const message = {
cancelKeepAlive: '取消保持頁面工作階段',
},
header: {
userCenter: '使用者中心',
exitSystem: '登出系統',
logout: '登出',
},
database: {
@@ -4020,7 +4022,6 @@ const message = {
dockerGuard: '容器連接埠防護',
systemFirewall: '主機防火牆',
systemFirewallHelper: '用於管理主機連接埠存取和入站規則。',
forwardPolicyDropWarning: '目前伺服器限制了網路轉送,轉送規則可能無法生效。',
forwardingHelper: '用於管理連接埠轉發規則。',
dockerFirewallHelper: '用於選擇 1Panel 容器連接埠防護的管理方式。',
dockerNftablesRequirement: 'Docker ≥ 29.0.0,實驗性',
@@ -4092,9 +4093,8 @@ const message = {
dockerInputUseHostFirewall: '此連接埠需要透過主機防火牆設定存取規則,請前往主機防火牆進行設定。',
dockerInputNotProtected: '主機 INPUT 規則無法直接保護此 Docker 發佈連接埠,點擊前往容器連接埠防護。',
notInitialized: '未初始化',
ipv6RARisk:
'IPv6 轉送已阻止:網路介面 {0} 的 accept_ra=1,可能依賴 RA/SLAAC。請確認網路設定,為需要接收 RA 的介面持久化設定 accept_ra=2 後重試。',
ipv6RACheckFailed: '無法檢查 IPv6 RA 設定,已阻止啟用 IPv6 轉送。請檢查系統網路設定後重試。',
ipv6RARisk: 'IPv6 轉送可能導致網路中斷,已阻止啟用。請參閱文件,詳情見系統日誌。',
ipv6RACheckFailed: '無法檢查 IPv6 網路設定,已阻止啟用轉送。詳情見系統日誌。',
ipv6ForwardingOnDemand: 'IPv6 轉送尚未啟用,將在新增或還原 IPv6 規則時檢查並啟用。',
familyUnsupported: '系統不支援 {0}',
familyChainIssue: '{0} · {1} 鏈 · {2}',
+4 -4
View File
@@ -552,6 +552,8 @@ const message = {
cancelKeepAlive: '取消保持页面会话',
},
header: {
userCenter: '用户中心',
exitSystem: '退出系统',
logout: '退出登录',
},
database: {
@@ -4066,7 +4068,6 @@ const message = {
dockerGuard: '容器端口防护',
systemFirewall: '主机防火墙',
systemFirewallHelper: '用于管理主机端口访问和入站规则。',
forwardPolicyDropWarning: '当前服务器限制了网络转发,转发规则可能无法生效。',
forwardingHelper: '用于管理端口转发规则。',
dockerFirewallHelper: '用于选择 1Panel 容器端口防护的管理方式。',
dockerNftablesRequirement: 'Docker ≥ 29.0.0,实验性',
@@ -4138,9 +4139,8 @@ const message = {
dockerInputUseHostFirewall: '该端口需要通过主机防火墙设置访问规则,请前往主机防火墙进行配置。',
dockerInputNotProtected: '宿主机 INPUT 规则不能直接保护此 Docker 发布端口,点击前往容器端口防护。',
notInitialized: '未初始化',
ipv6RARisk:
'IPv6 转发已阻止:网卡 {0} 的 accept_ra=1,可能依赖 RA/SLAAC。请确认网络配置,为需要接收 RA 的网卡持久化设置 accept_ra=2 后重试。',
ipv6RACheckFailed: '无法检查 IPv6 RA 配置,已阻止启用 IPv6 转发。请检查系统网络配置后重试。',
ipv6RARisk: 'IPv6 转发存在断网风险,已阻止启用。请参阅文档,详情见系统日志。',
ipv6RACheckFailed: '无法检查 IPv6 网络配置,已阻止启用转发。详情见系统日志。',
ipv6ForwardingOnDemand: 'IPv6 转发尚未启用,将在添加或恢复 IPv6 规则时检查并启用。',
familyUnsupported: '系统不支持 {0}',
familyChainIssue: '{0} · {1} 链 · {2}',
@@ -2,7 +2,7 @@
<div>
<el-popover
v-model:visible="popoverVisible"
placement="right-end"
placement="right-start"
:show-arrow="false"
:offset="0"
:width="200"
@@ -26,12 +26,6 @@
</div>
</template>
<div class="dropdown-menu" v-loading="loading || switchingNode">
<div class="dropdown-item" v-if="currentUser" @click="changeUserInfo">
<SvgIcon class="icon" iconName="p-gerenzhongxin1" />
{{ currentUser.name }}
</div>
<el-divider class="divider" />
<div class="dropdown-item" @click="openTask">
<SvgIcon class="icon" iconName="p-renwuzhongxin1" />
{{ $t('menu.msgCenter') }}
@@ -74,11 +68,6 @@
</el-icon>
</div>
</div>
<el-divider class="divider" />
<div class="dropdown-item" @click="logout">
<SvgIcon class="icon" iconName="p-tuichudenglu3" />
{{ $t('commons.login.logout') }}
</div>
</div>
</el-popover>
<NodeDrawer
@@ -129,7 +118,7 @@ const props = defineProps({
const defaultNodeLimit = 8;
const emit = defineEmits(['openTask', 'refresh']);
const emit = defineEmits(['openTask', 'refresh', 'userChange']);
bus.on('refreshTask', () => {
checkTask();
});
@@ -272,6 +261,7 @@ const openNodeDashboard = () => {
};
const logout = () => {
popoverVisible.value = false;
ElMessageBox.confirm(i18n.global.t('commons.msg.sureLogOut'), i18n.global.t('commons.msg.infoTitle'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
@@ -297,13 +287,17 @@ const loadCurrentUser = async (currentNode?: string) => {
const authInfo = await syncAuthInfo(currentNode);
if (authInfo) {
currentUser.value = authInfo;
emit('userChange', authInfo);
}
};
const changeUserInfo = () => {
popoverVisible.value = false;
loadCurrentUser();
userInfoRef.value?.openDrawer();
};
defineExpose({ openUserInfo: changeUserInfo, logout });
onMounted(() => {
loadNodes();
checkTask();
@@ -318,7 +312,7 @@ onMounted(() => {
display: flex;
align-items: center;
box-sizing: border-box;
border-top: 1px solid var(--panel-footer-border);
border-bottom: 1px solid var(--panel-footer-border);
height: 48px;
.icon {
margin-left: 25px;
@@ -10,6 +10,14 @@
<PrimaryMenu />
</div>
<Logo :isCollapse="isCollapse" />
<Collapse
ref="nodeSelectorRef"
class="node-selector"
:version="version"
@open-task="openTask"
@refresh="search"
@user-change="currentUser = $event"
/>
<el-scrollbar>
<el-menu
:default-active="activeMenu"
@@ -23,7 +31,40 @@
<SubItem :menuList="routerMenus" :level="0" />
</el-menu>
</el-scrollbar>
<Collapse :version="version" @open-task="openTask" @refresh="search" />
<el-dropdown
v-if="currentUser"
class="user-dropdown"
trigger="click"
placement="right-end"
:popper-options="{ modifiers: [{ name: 'flip', enabled: false }] }"
:show-arrow="false"
:hide-on-click="true"
@visible-change="userMenuVisible = $event"
@command="handleUserCommand"
>
<button
type="button"
class="user-entry"
:class="{ 'is-active': userMenuVisible }"
:aria-label="currentUser.name"
:title="currentUser.name"
>
<SvgIcon class="icon" iconName="p-gerenzhongxin1" />
<span v-if="!isCollapse" class="user-name">{{ currentUser.name }}</span>
</button>
<template #dropdown>
<el-dropdown-menu class="user-menu">
<el-dropdown-item command="userCenter">
<SvgIcon class="user-menu-icon" iconName="p-gerenzhongxin1" />
<span>{{ $t('header.userCenter') }}</span>
</el-dropdown-item>
<el-dropdown-item command="logout">
<SvgIcon class="user-menu-icon" iconName="p-tuichudenglu3" />
<span>{{ $t('header.exitSystem') }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
@@ -40,12 +81,24 @@ import { getSettingBaseInfo } from '@/api/modules/setting';
import PrimaryMenu from '@/assets/images/menu-bg.svg?component';
import { hasPermissionMetaAccess, hasRouteRoleAccess } from '@/utils/rbac';
import { useGlobalStore } from '@/composables/useGlobalStore';
import type { Login } from '@/api/interface/auth';
const route = useRoute();
const router = useRouter();
const menuStore = MenuStore();
const { currentNode, isAdmin, menuAccordion, permissions } = useGlobalStore();
const version = ref();
const nodeSelectorRef = ref<InstanceType<typeof Collapse>>();
const currentUser = ref<Login.AuthInfo>();
const userMenuVisible = ref(false);
const handleUserCommand = (command: string) => {
if (command === 'userCenter') {
nodeSelectorRef.value?.openUserInfo();
} else if (command === 'logout') {
nodeSelectorRef.value?.logout();
}
};
const activeMenu = computed(() => {
const { meta, path } = route;
@@ -324,8 +377,14 @@ watch(
height: 100%;
background: var(--panel-menu-bg-color) no-repeat top;
.logo,
.node-selector {
flex-shrink: 0;
}
.el-scrollbar {
flex: 1;
min-height: 0;
.el-menu {
overflow: auto;
overflow-x: hidden;
@@ -337,4 +396,62 @@ watch(
.ico {
height: 20px !important;
}
.user-dropdown {
flex-shrink: 0;
width: 100%;
}
.user-menu {
min-width: 160px;
:deep(.el-dropdown-menu__item) {
gap: 8px;
padding: 10px 16px;
}
.user-menu-icon {
flex-shrink: 0;
font-size: 8px;
}
}
.user-entry {
display: flex;
align-items: center;
gap: 7px;
flex-shrink: 0;
position: relative;
width: 100%;
height: 48px;
padding: 0 25px;
border: 0;
border-top: 1px solid var(--panel-footer-border);
background: transparent;
color: var(--panel-main-bg-color-1);
font-family: inherit;
font-size: var(--el-font-size-base);
line-height: 1.5;
text-align: left;
cursor: pointer;
.icon {
flex-shrink: 0;
font-size: 8px;
}
.user-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&:hover,
&:focus-visible,
&.is-active {
color: var(--el-color-primary);
background: var(--el-menu-item-bg-color);
}
}
</style>
+4 -6
View File
@@ -1,6 +1,4 @@
import { bindSearchReset } from '@/utils/search-reset';
import router from '@/routers/router';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { defineStore } from 'pinia';
import { ref, reactive, toRef } from 'vue';
import { MsgError } from '@/utils/message';
@@ -49,12 +47,12 @@ export const ProcessStore = defineStore('ProcessStore', () => {
port: undefined,
});
const { currentNode } = useGlobalStore();
bindSearchReset([() => router.currentRoute.value.path, currentNode], {
useSearchPersistence('host/process/process', {
pid: toRef(psSearch, 'pid'),
name: toRef(psSearch, 'name'),
username: toRef(psSearch, 'username'),
});
useSearchPersistence('host/process/network', {
processID: toRef(netSearch, 'processID'),
processName: toRef(netSearch, 'processName'),
port: toRef(netSearch, 'port'),
-12
View File
@@ -1,12 +0,0 @@
import { watch, type Ref, type WatchSource } from 'vue';
export type SearchFields = Record<string, Ref<string | number | undefined>>;
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;
};
watch(sources, reset, { flush: 'sync' });
return reset;
};
@@ -172,7 +172,7 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { computed, ref, watch } from 'vue';
import { Refresh, Search } from '@element-plus/icons-vue';
import { ElMessageBox } from 'element-plus';
@@ -193,16 +193,10 @@ const searching = ref(false);
const operating = ref('');
const keyword = ref('');
const marketKeyword = ref('');
useSearchReset(
useSearchPersistence(
'ai/agents/agent/config/tabs/plugins',
{ keyword, marketKeyword },
{
scope: () => agentId.value || undefined,
onRevisit: () => {
page.value = 1;
marketResults.value = [];
marketSearched.value = false;
},
},
() => agentId.value || undefined,
);
const origin = ref('');
const status = ref('');
@@ -138,7 +138,7 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { computed, ref, watch } from 'vue';
import { Refresh, Search } from '@element-plus/icons-vue';
import { useI18n } from 'vue-i18n';
@@ -169,15 +169,10 @@ const getDefaultMarketSource = (): HermesSkillMarketSource => {
const marketSource = ref<HermesSkillMarketSource>(getDefaultMarketSource());
const marketSearched = ref(false);
const agentId = ref(0);
useSearchReset(
useSearchPersistence(
'ai/agents/agent/config/tabs/skills/hermes',
{ installedKeyword, marketKeyword },
{
scope: () => agentId.value || undefined,
onRevisit: () => {
marketResults.value = [];
marketSearched.value = false;
},
},
() => agentId.value || undefined,
);
const installedSkills = ref<AI.AgentSkillItem[]>([]);
const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
@@ -142,7 +142,7 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { computed, ref, watch } from 'vue';
import { Refresh, Search } from '@element-plus/icons-vue';
import { useI18n } from 'vue-i18n';
@@ -181,15 +181,10 @@ const getDefaultMarketSource = (): SkillMarketSource => {
const marketSource = ref<SkillMarketSource>(getDefaultMarketSource());
const marketSearched = ref(false);
const agentId = ref(0);
useSearchReset(
useSearchPersistence(
'ai/agents/agent/config/tabs/skills/openclaw',
{ installedKeyword, marketKeyword },
{
scope: () => agentId.value || undefined,
onRevisit: () => {
marketResults.value = [];
marketSearched.value = false;
},
},
() => agentId.value || undefined,
);
const skills = ref<AI.AgentSkillItem[]>([]);
const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
+3 -3
View File
@@ -272,8 +272,8 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { toRef, defineAsyncComponent, onMounted, reactive, ref } from 'vue';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { deleteAgentCheck, pageAgents, resetAgentToken, unbindAgentWebsite, updateAgentRemark } from '@/api/modules/ai';
import { checkAppInstalled, installedOp, searchApp, searchAppInstalled } from '@/api/modules/app';
@@ -341,6 +341,7 @@ const isActive = ref(false);
const isExist = ref(false);
const noApp = ref(false);
const searchName = ref('');
useSearchPersistence('ai/agents/agent/index', { search: searchName });
const defaultHttpsPort = ref(443);
const openrestyPortLoaded = ref(false);
const websiteDomainsMap = ref<Record<number, Website.Domain[]>>({});
@@ -400,7 +401,6 @@ const paginationConfig = reactive({
pageSize: 10,
total: 0,
});
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
const checkNoApp = async () => {
try {
+3 -3
View File
@@ -69,8 +69,8 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { toRef, onMounted, reactive, ref } from 'vue';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { onMounted, reactive, ref } from 'vue';
import { deleteAgentAccount, getAgentProviders, pageAgentAccounts } from '@/api/modules/ai';
import { AI } from '@/api/interface/ai';
import AddDialog from '@/views/ai/agents/model/add/index.vue';
@@ -86,6 +86,7 @@ const items = ref<AI.AgentAccountItem[]>([]);
const addRef = ref();
const modelPoolRef = ref();
const searchName = ref('');
useSearchPersistence('ai/agents/model/index', { search: searchName });
const apiType = ref('');
const apiTypeOptions = ref<string[]>([]);
@@ -111,7 +112,6 @@ const paginationConfig = reactive({
pageSize: 10,
total: 0,
});
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
const search = async () => {
const req: AI.AgentAccountSearch = {
+3 -3
View File
@@ -186,7 +186,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
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';
@@ -195,7 +195,7 @@ import Terminal from '@/views/ai/model/ollama/terminal/index.vue';
import Del from '@/views/ai/model/ollama/del/index.vue';
import PortJumpDialog from '@/components/port-jump/index.vue';
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import i18n from '@/lang';
import { App } from '@/api/interface/app';
import {
@@ -237,7 +237,7 @@ const paginationConfig = reactive({
total: 0,
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('ai/model/ollama/index', { search: searchName });
const appInstallID = ref(0);
const opRef = ref();
+2 -5
View File
@@ -70,7 +70,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { App } from '@/api/interface/app';
import { toRef, onMounted, reactive, ref } from 'vue';
import { searchApp, syncApp, syncCutomAppStore, syncLocalApp, getCurrentNodeCustomAppConfig } from '@/api/modules/app';
@@ -105,10 +105,7 @@ const req = reactive({
resource: 'all',
showCurrentArch: false,
});
useSearchReset(
{ search: toRef(req, 'name'), page: toRef(paginationConfig, 'currentPage') },
{ onRevisit: () => search(req) },
);
useSearchPersistence('app-store/apps/index', { search: toRef(req, 'name') });
const apps = ref<App.AppItem[]>([]);
const loading = ref(false);
@@ -139,7 +139,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import AppCard from '@/views/app-store/installed/app/card.vue';
import Backups from '@/components/backup/index.vue';
import Uploads from '@/components/upload/index.vue';
@@ -211,10 +211,7 @@ const searchReq = reactive({
update: false,
sync: false,
});
useSearchReset(
{ search: toRef(searchReq, 'name'), page: toRef(paginationConfig, 'currentPage') },
{ onRevisit: () => search() },
);
useSearchPersistence('app-store/installed/index', { search: toRef(searchReq, 'name') });
const router = useRouter();
const activeName = ref(i18n.global.t('app.installed'));
const mode = ref('installed');
@@ -521,7 +521,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { computed, h, ref } from 'vue';
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import ContainerLog from '@/components/log/container/index.vue';
@@ -576,7 +576,7 @@ const dialogBackupRef = ref();
const uploadRef = ref();
const searchName = ref('');
useSearchReset({ search: searchName }, { onRevisit: () => search() });
useSearchPersistence('container/compose/index', { search: searchName });
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 { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
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';
@@ -490,7 +490,7 @@ import Uploads from '@/components/upload/index.vue';
import DockerStatus from '@/views/container/docker-status/index.vue';
import ContainerLogDialog from '@/components/log/container-drawer/index.vue';
import Status from '@/components/status/index.vue';
import { toRef, computed, reactive, ref } from 'vue';
import { computed, reactive, ref } from 'vue';
import {
containerItemStats,
containerListStats,
@@ -536,7 +536,7 @@ const paginationConfig = reactive({
order: 'ascending',
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('container/container/index', { search: searchName });
const dialogUpgradeRef = ref();
const dialogCommitRef = ref();
const dialogPortJumpRef = ref();
+2 -5
View File
@@ -157,7 +157,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { toRef, reactive, ref, computed } from 'vue';
import { dateFormat } from '@/utils/date';
import { newUUID } from '@/utils/id';
@@ -200,10 +200,7 @@ const paginationConfig = reactive({
orderBy: 'createdAt',
order: 'null',
});
useSearchReset(
{ search: toRef(paginationConfig, 'name'), page: toRef(paginationConfig, 'currentPage') },
{ onRevisit: () => search() },
);
useSearchPersistence('container/image/index', { search: toRef(paginationConfig, 'name') });
const columns = ref([]);
const isActive = ref(false);
@@ -95,10 +95,10 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import CreateDialog from '@/views/container/network/create/index.vue';
import DetailDrawer from '@/views/container/network/detail/index.vue';
import { toRef, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import { dateFormat } from '@/utils/date';
import { newUUID } from '@/utils/id';
import { deleteNetwork, searchNetwork, inspect, containerPrune } from '@/api/modules/container';
@@ -121,7 +121,7 @@ const paginationConfig = reactive({
total: 0,
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('container/network/index', { search: searchName });
const opRef = ref();
const isActive = ref(false);
+3 -3
View File
@@ -59,9 +59,9 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import OperatorDialog from '@/views/container/repo/operator/index.vue';
import { toRef, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import { dateFormat } from '@/utils/date';
import { Container } from '@/api/interface/container';
import { checkRepoStatus, deleteImageRepo, searchImageRepo } from '@/api/modules/container';
@@ -78,7 +78,7 @@ const paginationConfig = reactive({
total: 0,
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('container/repo/index', { search: searchName });
const opRef = ref();
const confirmDialog = ref();
@@ -81,8 +81,8 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { toRef, reactive, ref } from 'vue';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { reactive, ref } from 'vue';
import { dateFormat, getCurrentDateFormatted } from '@/utils/date';
import { downloadWithContent } from '@/utils/file';
import { Container } from '@/api/interface/container';
@@ -111,7 +111,7 @@ const paginationConfig = reactive({
total: 0,
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('container/template/index', { search: searchName });
const search = async () => {
if (!isActive.value || !isExist.value) {
@@ -124,11 +124,11 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
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';
import { toRef, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import { dateFormat } from '@/utils/date';
import { newUUID } from '@/utils/id';
import { deleteVolume, searchVolume, inspect, containerPrune } from '@/api/modules/container';
@@ -158,7 +158,7 @@ const paginationConfig = reactive({
total: 0,
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('container/volume/index', { search: searchName });
const isActive = ref(false);
const isExist = ref(false);
+3 -3
View File
@@ -272,11 +272,11 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
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';
import { toRef, onMounted, ref, toRefs } from 'vue';
import { onMounted, ref, toRefs } from 'vue';
import {
deleteCronjob,
editCronjobGroup,
@@ -332,7 +332,7 @@ const pageState = usePageState(() => ({
}));
const paginationConfig = pageState.paginationConfig;
const { defaultGroupID, searchName, searchGroupID } = toRefs(pageState);
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('cronjob/cronjob/index', { search: searchName });
const search = async (column?: any) => {
if (column) {
+3 -3
View File
@@ -132,7 +132,7 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { dateFormat, getCurrentDateFormatted } from '@/utils/date';
import { deepCopy } from '@/utils/misc';
import { newUUID } from '@/utils/id';
@@ -141,7 +141,7 @@ import TaskLog from '@/components/log/task/index.vue';
import OperateDialog from '@/views/cronjob/library/operate/index.vue';
import TerminalDialog from '@/views/cronjob/library/run/index.vue';
import { deleteScript, searchScript, syncScript } from '@/api/modules/cronjob';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { Cronjob } from '@/api/interface/cronjob';
import i18n from '@/lang';
import { useGlobalStore } from '@/composables/useGlobalStore';
@@ -170,7 +170,7 @@ const paginationConfig = reactive({
total: 0,
});
const searchInfo = ref<string>('');
useSearchReset({ search: searchInfo, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('cronjob/library/index', { search: searchInfo });
const group = ref<string>('');
const groupOptions = ref();
@@ -323,8 +323,8 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { toRef, nextTick, onMounted, reactive, ref } from 'vue';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { nextTick, onMounted, reactive, ref } from 'vue';
import { dateFormat } from '@/utils/date';
import { getRandomStr } from '@/utils/id';
import { Position } from '@element-plus/icons-vue';
@@ -366,6 +366,7 @@ const loading = ref(false);
const maskShow = ref(true);
const submitLoading = ref(false);
const searchName = ref('');
useSearchPersistence('database/mongodb/index', { search: searchName });
const createVisible = ref(false);
const appStatusRef = ref();
const bindRef = ref();
@@ -399,7 +400,6 @@ const paginationConfig = reactive({
orderBy: 'createdAt',
order: 'null',
});
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
@@ -75,9 +75,9 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { dateFormat } from '@/utils/date';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
import AppResources from '@/views/database/postgresql/check/index.vue';
import OperateDialog from '@/views/database/mongodb/remote/operate/index.vue';
@@ -101,7 +101,7 @@ const paginationConfig = reactive({
order: 'null',
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('database/mongodb/remote/index', { search: searchName });
const search = async (column?: any) => {
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
+3 -3
View File
@@ -226,7 +226,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
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';
@@ -241,7 +241,7 @@ import PortJumpDialog from '@/components/port-jump/index.vue';
import Tooltip from '@/components/tooltip/index.vue';
import { dateFormat } from '@/utils/date';
import { ElMessageBox } from 'element-plus';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import {
deleteCheckMysqlDB,
listDatabases,
@@ -301,7 +301,7 @@ const paginationConfig = reactive({
order: 'null',
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('database/mysql/index', { search: searchName });
const mysqlContainer = ref();
const mysqlStatus = ref();
@@ -74,9 +74,9 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { dateFormat } from '@/utils/date';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
import AppResources from '@/views/database/mysql/check/index.vue';
import OperateDialog from '@/views/database/mysql/remote/operate/index.vue';
@@ -100,7 +100,7 @@ const paginationConfig = reactive({
order: 'null',
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('database/mysql/remote/index', { search: searchName });
const search = async (column?: any) => {
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
@@ -241,7 +241,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
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';
@@ -256,7 +256,7 @@ import UploadDialog from '@/components/upload/index.vue';
import PortJumpDialog from '@/components/port-jump/index.vue';
import Tooltip from '@/components/tooltip/index.vue';
import { dateFormat } from '@/utils/date';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import {
deleteCheckPostgresqlDB,
listDatabases,
@@ -313,7 +313,7 @@ const paginationConfig = reactive({
order: 'null',
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('database/postgresql/index', { search: searchName });
const postgresqlContainer = ref();
const postgresqlStatus = ref();
@@ -74,9 +74,9 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { dateFormat } from '@/utils/date';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
import AppResources from '@/views/database/postgresql/check/index.vue';
import OperateDialog from '@/views/database/postgresql/remote/operate/index.vue';
@@ -100,7 +100,7 @@ const paginationConfig = reactive({
order: 'null',
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('database/postgresql/remote/index', { search: searchName });
const search = async (column?: any) => {
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
+50 -25
View File
@@ -81,7 +81,13 @@
:style="{ height: `calc(100vh - ${loadHeight()})`, 'background-color': '#000' }"
:description="loadErrMsg()"
>
<el-button v-if="currentDB.from === 'remote'" v-permission type="primary" @click="installCli">
<el-button
v-if="currentDB.from === 'remote'"
v-permission
type="primary"
:loading="cliSubmitting"
@click="installCli"
>
{{ $t('commons.button.enable') }}
</el-button>
</el-empty>
@@ -134,6 +140,7 @@
</DialogPro>
<QuickCmd ref="dialogQuickCmdRef" @reload="loadQuickCmd" />
<TaskLog ref="cliTaskLogRef" @close="checkCliValid" />
</div>
</template>
@@ -148,12 +155,14 @@ import { checkAppInstalled } from '@/api/modules/app';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { listDatabases, checkRedisCli, installRedisCli } from '@/api/modules/database';
import { Database } from '@/api/interface/database';
import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang';
import { getCommandList } from '@/api/modules/command';
import { routerToName, routerToNameWithQuery } from '@/utils/router';
import { useOperateNodeContext } from '@/composables/useOperateNodeContext';
import TaskLog from '@/components/log/task/index.vue';
import { newUUID } from '@/utils/id';
const { currentNode, currentRedisDB, openMenuTabs } = useGlobalStore();
useOperateNodeContext(currentNode);
@@ -170,7 +179,11 @@ const appStatusRef = ref();
const open = ref(false);
const redisCliExist = ref();
const redisCliExist = ref(false);
const cliSubmitting = ref(false);
const cliTaskLogRef = ref<InstanceType<typeof TaskLog>>();
const operateNode = currentNode.value;
let disposed = false;
const appKey = ref('redis');
const appName = ref();
@@ -250,6 +263,7 @@ const changeDatabase = async () => {
const loadDBOptions = async () => {
try {
const res = await listDatabases('redis,redis-cluster');
if (disposed || operateNode !== currentNode.value) return;
let datas = res.data || [];
dbOptionsLocal.value = [];
dbOptionsRemote.value = [];
@@ -286,7 +300,7 @@ const loadDBOptions = async () => {
reOpenTerminal();
}
} finally {
isLoaded.value = true;
if (!disposed && operateNode === currentNode.value) isLoaded.value = true;
}
};
@@ -301,6 +315,9 @@ const reOpenTerminal = async () => {
};
const initTerminal = async () => {
if (disposed || !currentDB.value) return;
const database = currentDBName.value;
const isCurrent = () => !disposed && operateNode === currentNode.value && database === currentDBName.value;
loading.value = true;
if (currentDB.value.from === 'remote') {
if (!redisCliExist.value) {
@@ -311,9 +328,10 @@ const initTerminal = async () => {
loading.value = false;
redisIsExist.value = true;
nextTick(() => {
if (!isCurrent()) return;
terminalShow.value = true;
redisStatus.value = 'Running';
terminalRef.value.acceptParams({
terminalRef.value?.acceptParams({
endpoint: '/api/v2/hosts/terminal/container',
args: `source=redis&name=${currentDBName.value}&from=${currentDB.value.from}`,
error: '',
@@ -325,13 +343,15 @@ const initTerminal = async () => {
}
await checkAppInstalled(currentDB.value.type, currentDBName.value)
.then((res) => {
if (!isCurrent()) return;
redisIsExist.value = res.data.isExist;
redisStatus.value = res.data.status;
loading.value = false;
nextTick(() => {
if (!isCurrent()) return;
if (res.data.status === 'Running') {
terminalShow.value = true;
terminalRef.value.acceptParams({
terminalRef.value?.acceptParams({
endpoint: '/api/v2/hosts/terminal/container',
args: `source=${currentDB.value.type}&name=${currentDBName.value}&from=${currentDB.value.from}`,
error: '',
@@ -342,6 +362,7 @@ const initTerminal = async () => {
isRefresh.value = !isRefresh.value;
})
.catch(() => {
if (!isCurrent()) return;
closeTerminal(false);
loading.value = false;
});
@@ -353,27 +374,30 @@ const closeTerminal = async (isKeepShow: boolean) => {
};
const checkCliValid = async () => {
await checkRedisCli()
.then((res) => {
redisCliExist.value = res.data;
loadDBOptions();
})
.catch(() => {
loadDBOptions();
});
if (disposed || operateNode !== currentNode.value) return;
try {
const res = await checkRedisCli();
if (disposed || operateNode !== currentNode.value) return;
redisCliExist.value = res.data;
} finally {
if (!disposed && operateNode === currentNode.value) await loadDBOptions();
}
};
const installCli = async () => {
loading.value = true;
await installRedisCli()
.then(() => {
loading.value = false;
redisCliExist.value = true;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
reOpenTerminal();
})
.catch(() => {
loading.value = false;
});
if (cliSubmitting.value) return;
cliSubmitting.value = true;
try {
const res = await installRedisCli(newUUID(), operateNode);
if (disposed || operateNode !== currentNode.value) return;
if (res.data) {
cliTaskLogRef.value?.openWithTaskID(res.data, true, operateNode);
} else {
await checkCliValid();
}
} finally {
cliSubmitting.value = false;
}
};
const loadQuickCmd = async () => {
@@ -403,6 +427,7 @@ const onAfter = () => {
initTerminal();
};
onBeforeUnmount(() => {
disposed = true;
closeTerminal(false);
});
</script>
@@ -73,9 +73,9 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { dateFormat } from '@/utils/date';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
import AppResources from '@/views/database/redis/check/index.vue';
import OperateDialog from '@/views/database/redis/remote/operate/index.vue';
@@ -99,7 +99,7 @@ const paginationConfig = reactive({
order: 'null',
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('database/redis/remote/index', { search: searchName });
const search = async (column?: any) => {
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
@@ -739,7 +739,7 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { toRef, computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import {
addFavorite,
@@ -853,6 +853,7 @@ const initData = () => ({
sortOrder: 'ascending',
});
let req = reactive(initData());
useSearchPersistence('host/file-management/index', { search: toRef(req, 'search') });
let loading = ref(false);
const paths = ref<FilePaths[]>([]);
const breadcrumbVisiblePaths = ref<FilePaths[]>([]);
@@ -985,10 +986,6 @@ const paginationConfig = reactive({
pageSize: Number(localStorage.getItem('file-page-size')) || 100,
total: 0,
});
useSearchReset(
{ search: toRef(req, 'search'), page: toRef(paginationConfig, 'currentPage') },
{ onRevisit: () => search() },
);
const btnWrapperRefs = ref<Record<string, any>>({});
@@ -2854,6 +2851,36 @@ onBeforeUnmount(() => {
border-bottom: 1px solid var(--el-border-color-light) !important;
}
/* Keep the closable tab width stable so switching tabs does not reflow the tab bar */
:deep(.file-tabs .el-tabs__header .el-tabs__item.is-closable) {
padding: 0 20px;
.is-icon-close {
width: 14px;
margin-left: 5px;
right: 0;
opacity: 0;
pointer-events: none;
transition: opacity var(--el-transition-duration);
}
&.is-active,
&:hover,
&:focus-within {
.is-icon-close {
opacity: 1;
pointer-events: auto;
}
}
@media (hover: none), (pointer: coarse) {
.is-icon-close {
opacity: 1;
pointer-events: auto;
}
}
}
:deep(.file-tabs .el-tabs--card > .el-tabs__header .el-tabs__item.is-active) {
border-bottom-width: 1px !important;
}
@@ -6,7 +6,6 @@
:closable="false"
:title="$t('firewall.importLimit', [FIREWALL_BATCH_LIMIT, FIREWALL_IMPORT_MAX_SIZE / 1024])"
/>
<el-alert v-if="submitError" class="mb-3" type="error" :closable="false" :title="submitError" />
<div class="import-file-bar mt-3">
<el-upload
ref="uploadRef"
@@ -100,7 +99,6 @@ const policies = ref<Firewall.DockerGuardPolicy[]>([]);
const selects = ref(new Set<Firewall.DockerGuardPolicy>());
const uploadRef = ref();
const uploaderFiles = ref<UploadFile[]>([]);
const submitError = ref('');
const displaySources = (policy: Firewall.DockerGuardPolicy) => formatHostAddressList(policy.sources, policy.family);
const fileOnChange = (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
@@ -109,7 +107,6 @@ const fileOnChange = (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
policies.value = [];
selects.value = new Set();
submitError.value = '';
uploaderFiles.value = uploadFiles;
if (uploadFile.raw.size > FIREWALL_IMPORT_MAX_SIZE) {
uploadRef.value?.clearFiles();
@@ -162,7 +159,6 @@ const onImport = async () => {
return;
}
loading.value = true;
submitError.value = '';
try {
const result = (
await upsertDockerPortGuardPolicies({
@@ -171,16 +167,17 @@ const onImport = async () => {
})
).data;
if (!result.taskID || !result.queued) {
submitError.value = i18n.global.t('commons.msg.operationFailed');
MsgError(i18n.global.t('commons.msg.operationFailed'));
return;
}
visible.value = false;
emit('created', result.taskID);
} catch (error) {
submitError.value =
MsgError(
(isAxiosError(error) && error.response?.data?.message) ||
(error && getErrorMessage(error)) ||
i18n.global.t('commons.res.commonError');
(error && getErrorMessage(error)) ||
i18n.global.t('commons.res.commonError'),
);
} finally {
loading.value = false;
}
@@ -200,7 +197,6 @@ const acceptParams = () => {
policies.value = [];
selects.value = new Set();
uploaderFiles.value = [];
submitError.value = '';
uploadRef.value?.clearFiles();
visible.value = true;
};
@@ -12,8 +12,7 @@
v-loading="loading"
>
<template #prompt>
<el-alert v-if="data.base.message" type="warning" :closable="false" :title="data.base.message" />
<el-alert v-else type="info" :closable="false" :title="$t('firewall.dockerGuardHelper')" />
<el-alert type="info" :closable="false" :title="$t('firewall.dockerGuardHelper')" />
</template>
<template #leftToolBar>
<el-button plain @click="orphanDrawerVisible = true">
@@ -220,7 +219,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { computed, reactive, ref, watch } from 'vue';
import FireRouter from '@/views/host/firewall/index.vue';
import DockerGuardStatus from '@/views/host/firewall/docker/status/index.vue';
@@ -264,14 +263,7 @@ const openRuleTask = (taskID: string) => taskLogRef.value?.openWithTaskID(taskID
const orphanDrawerVisible = ref(false);
const orphanSearchName = ref('');
const searchName = ref('');
useSearchReset(
{ search: searchName },
{
onRevisit: () => {
paginationConfig.currentPage = 1;
},
},
);
useSearchPersistence('host/firewall/docker/index', { search: searchName });
const selects = ref<Firewall.DockerGuardContainer[]>([]);
const orphanSelects = ref<Firewall.DockerGuardEndpoint[]>([]);
const data = reactive<Firewall.DockerGuardList>({
@@ -462,6 +454,7 @@ const search = async () => {
loading.value = true;
try {
Object.assign(data, (await loadDockerPortGuard()).data);
if (data.base.message) MsgError(data.base.message);
selects.value = [];
orphanSelects.value = [];
} finally {
@@ -25,9 +25,6 @@
<span v-else>{{ $t('firewall.basicStatus') }}</span>
</el-card>
<LayoutContent :title="$t('firewall.forwardRule', 2)" :class="{ mask: !isInit || !isBind }">
<template v-if="fireStatusRef?.forwardDropFamilies" #prompt>
<el-alert type="warning" :closable="false" :title="$t('firewall.forwardPolicyDropWarning')" />
</template>
<template #leftToolBar>
<el-button v-permission v-node-admin type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}
@@ -120,14 +117,14 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import OperateDialog from './operate/index.vue';
import ImportDialog from './import/index.vue';
import FireRouter from '@/views/host/firewall/index.vue';
import FireStatus from '@/views/host/firewall/status/index.vue';
import RuleReset from '@/views/host/firewall/components/rule-reset.vue';
import TaskLog from '@/components/log/task/index.vue';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { resetFirewallRules, operateForwardRule, searchForwardRule } from '@/api/modules/firewall';
import { Firewall } from '@/api/interface/firewall';
import { buildForwardRuleExport } from './transfer';
@@ -141,6 +138,7 @@ import { ElMessageBox } from 'element-plus';
const loading = ref();
const selects = ref<any>([]);
const searchName = ref();
useSearchPersistence('host/firewall/forward/index', { search: searchName });
const isInit = ref(false);
const isBind = ref(false);
@@ -191,7 +189,6 @@ const paginationConfig = reactive({
pageSize: Number(localStorage.getItem('firewall-forward-page-size')) || 20,
total: 0,
});
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
const search = async () => {
if (!isInit.value || !isBind.value || fireName.value === '-') {
@@ -302,7 +302,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { Firewall } from '@/api/interface/firewall';
import type { FuTableOperationButton } from '@/components/table/shared';
import { buildHostRuleExport } from './transfer';
@@ -330,7 +330,7 @@ import RuleReset from '@/views/host/firewall/components/rule-reset.vue';
import TaskLog from '@/components/log/task/index.vue';
import DockerRestart from '@/components/docker-proxy/docker-restart.vue';
import { loadDockerStatus } from '@/api/modules/container';
import { toRef, computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import { useElementSize } from '@vueuse/core';
import { ElMessageBox } from 'element-plus';
import { Expand, Loading, Lock, Refresh } from '@element-plus/icons-vue';
@@ -405,6 +405,7 @@ const showFirewallUnavailablePrompt = computed(
const firewallVersion = ref('');
const selectedRuleFilters = ref<RuleFilter[]>(loadCachedFilterValues(ruleFilterStorageKey, ruleFilterOptions, []));
const searchName = ref('');
useSearchPersistence('host/firewall/rule/index', { search: searchName });
const inventoryItems = ref<Firewall.InventoryItem[]>([]);
const positionRanges = ref<Partial<Record<Firewall.Family, Firewall.PositionRange>>>({});
const inventoryTotal = ref(0);
@@ -432,7 +433,6 @@ const paginationConfig = reactive({
pageSize: Number(localStorage.getItem('firewall-rule-page-size')) || 20,
total: 0,
});
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
const providerScopes = (): Firewall.Scope[] => {
if (provider.value === 'iptables' || provider.value === 'nftables') {
@@ -15,7 +15,7 @@
</template>
</i18n-t>
</NoSuchService>
<div class="app-status card-interval" v-else-if="baseInfo.isExist && !baseInfo.message">
<div class="app-status card-interval" v-else-if="baseInfo.isExist">
<el-card>
<div class="flex w-full flex-col gap-4 md:flex-row">
<div class="flex flex-wrap gap-4 ml-3">
@@ -145,16 +145,6 @@
:title="$t('firewall.directBackendConflictWarning', [backendName, baseInfo.conflictBackend])"
/>
</div>
<el-alert v-else-if="baseInfo.isExist" class="card-interval" type="error" show-icon :closable="false">
<template #title>
<div class="flex items-center gap-2">
<span>{{ baseInfo.message }}</span>
<el-button v-permission v-node-admin type="primary" link @click="goToFirewallSetting">
{{ $t('commons.button.set') }}
</el-button>
</div>
</template>
</el-alert>
<DockerRestart
ref="dockerRef"
v-model:withDockerRestart="withDockerRestart"
@@ -210,7 +200,6 @@ const baseInfo = ref<Firewall.FirewallBase>({
conflictBackend: '',
version: '',
pingStatus: '',
message: '',
reason: '',
ipv4: { available: false, initialized: false, bound: false },
ipv6: { available: false, initialized: false, bound: false },
@@ -243,13 +232,6 @@ const familyStatuses = computed(() =>
).filter((item) => item.family === 'IPv4' || baseInfo.value.ipv6Enabled !== false),
);
const availableFamilies = computed(() => familyStatuses.value.filter((item) => item.status.available));
const forwardDropFamilies = computed(() => {
if (backendUnavailable.value || !baseInfo.value.isExist || baseInfo.value.message) return '';
return familyStatuses.value
.filter((item) => item.status.forwardPolicy === 'DROP')
.map((item) => item.family)
.join(', ');
});
const anyFamilyInitialized = computed(() => availableFamilies.value.some((item) => item.status.initialized));
const allAvailableFamiliesInitialized = computed(
() => availableFamilies.value.length > 0 && availableFamilies.value.every((item) => item.status.initialized),
@@ -297,7 +279,7 @@ const familyActions = computed(() =>
const familyIssueText = (issue: FamilyIssue) => {
if (!issue.available) return i18n.global.t('firewall.familyUnsupported', [issue.family]);
if (issue.reason === 'ipv6_ra_required') {
return i18n.global.t('firewall.ipv6RARisk', [issue.raInterfaces?.join(', ') || '-']);
return i18n.global.t('firewall.ipv6RARisk');
}
if (issue.reason === 'ipv6_ra_check_failed') return i18n.global.t('firewall.ipv6RACheckFailed');
if (issue.partial) return i18n.global.t('firewall.familyIncomplete', [issue.family]);
@@ -481,7 +463,6 @@ onBeforeUnmount(() => {
});
defineExpose({
forwardDropFamilies,
acceptParams,
openInitialization: onInit,
});
+3 -3
View File
@@ -93,11 +93,11 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { dateFormatServerTimezone } from '@/utils/date';
import { downloadFile } from '@/utils/file';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { cleanSSHLogs, exportSSHLogs, loadSSHLogs } from '@/api/modules/host';
import { useGlobalStore } from '@/composables/useGlobalStore';
import i18n from '@/lang';
@@ -120,7 +120,7 @@ const exportConfig = reactive({
status: 'All',
});
const searchInfo = ref();
useSearchReset({ search: searchInfo, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('host/ssh/log/log', { search: searchInfo });
const searchStatus = ref('All');
const search = async () => {
@@ -24,7 +24,7 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import FireRouter from '@/views/host/ssh/index.vue';
import { toRef, ref, onMounted, onUnmounted, reactive } from 'vue';
import i18n from '@/lang';
@@ -38,7 +38,7 @@ const sshSearch = reactive({
type: 'ssh',
loginUser: '',
});
useSearchReset({ search: toRef(sshSearch, 'loginUser') }, { onRevisit: () => search() });
useSearchPersistence('host/ssh/session/index', { search: toRef(sshSearch, 'loginUser') });
const buttons = [
{
+4 -6
View File
@@ -101,7 +101,7 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import { Log } from '@/api/interface/log';
import { getSystemLogStatus, listRunningServices, readSystemLogs } from '@/api/modules/log';
@@ -114,7 +114,7 @@ const logs = ref<Log.SystemLogItem[]>([]);
const loading = ref(false);
const watching = ref(false);
const keyword = ref('');
useSearchReset({ keyword }, { onRevisit: () => scheduleFilterSearch() });
useSearchPersistence('log/host-system/index', { keyword });
const priority = ref('');
const service = ref('');
const services = ref<string[]>([]);
@@ -274,13 +274,11 @@ onUnmounted(() => {
if (filterTimer) clearTimeout(filterTimer);
});
const scheduleFilterSearch = () => {
watch([keyword, priority, service], () => {
requestID++;
if (filterTimer) clearTimeout(filterTimer);
filterTimer = setTimeout(resetAndLoadLogs, 300);
};
watch([keyword, priority, service], scheduleFilterSearch);
});
</script>
<style scoped lang="scss">
+3 -3
View File
@@ -45,12 +45,12 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import LogRouter from '@/views/log/router/index.vue';
import { dateFormat } from '@/utils/date';
import { cleanLogs, getLoginLogs } from '@/api/modules/log';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { useGlobalStore } from '@/composables/useGlobalStore';
@@ -67,7 +67,7 @@ const paginationConfig = reactive({
total: 0,
});
const searchInfo = ref<string>('');
useSearchReset({ search: searchInfo, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('log/login/index', { search: searchInfo });
const searchStatus = ref<string>('');
const search = async () => {
+3 -3
View File
@@ -125,12 +125,12 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import LogRouter from '@/views/log/router/index.vue';
import { dateFormat } from '@/utils/date';
import { cleanLogs, getOperationLogs } from '@/api/modules/log';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { useGlobalStore } from '@/composables/useGlobalStore';
@@ -146,7 +146,7 @@ const paginationConfig = reactive({
total: 0,
});
const searchName = ref<string>('');
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('log/operation/index', { search: searchName });
const searchGroup = ref<string>('');
const searchStatus = ref<string>('');
const searchNode = ref<string>('');
@@ -120,7 +120,7 @@
</div>
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { dateFormat } from '@/utils/date';
import { toRef, onMounted, ref } from 'vue';
import { searchBackup, deleteBackup, refreshToken } from '@/api/modules/backup';
@@ -143,10 +143,7 @@ const paginationConfig = reactive({
type: '',
name: '',
});
useSearchReset(
{ search: toRef(paginationConfig, 'name'), page: toRef(paginationConfig, 'currentPage') },
{ onRevisit: () => search() },
);
useSearchPersistence('setting/backup-account/index', { search: toRef(paginationConfig, 'name') });
const opRef = ref();
const dialogRef = ref();
const detailRef = ref();
@@ -225,7 +225,7 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import {
searchSnapshotPage,
snapshotDelete,
@@ -233,7 +233,7 @@ import {
snapshotRollback,
updateSnapshotDescription,
} from '@/api/modules/setting';
import { toRef, onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { computeSize } from '@/utils/size';
import { dateFormat } from '@/utils/date';
import { newUUID } from '@/utils/id';
@@ -261,7 +261,7 @@ const paginationConfig = reactive({
order: 'null',
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('setting/snapshot/index', { search: searchName });
const opRef = ref();
@@ -100,13 +100,13 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { Command } from '@/api/interface/command';
import GroupDialog from '@/components/group/index.vue';
import OperateDialog from '@/views/terminal/command/operate/index.vue';
import ImportDialog from '@/views/terminal/command/import/index.vue';
import { editCommand, deleteCommand, getCommandPage, exportCommands } from '@/api/modules/command';
import { toRef, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { getGroupList } from '@/api/modules/group';
@@ -124,7 +124,7 @@ const paginationConfig = reactive({
order: 'ascending',
});
const info = ref();
useSearchReset({ search: info, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('terminal/command/index', { search: info });
const group = ref<string>('');
const dialogRef = ref();
const opRef = ref();
+3 -3
View File
@@ -79,12 +79,12 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import GroupDialog from '@/components/agent-group/index.vue';
import OperateDialog from '@/views/terminal/host/operate/index.vue';
import { deleteHost, editHostGroup, searchHosts } from '@/api/modules/terminal';
import { getAgentGroupList } from '@/api/modules/group';
import { toRef, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { Host } from '@/api/interface/host';
import { MsgSuccess } from '@/utils/message';
@@ -100,7 +100,7 @@ const paginationConfig = reactive({
total: 0,
});
const info = ref();
useSearchReset({ search: info, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('terminal/host/index', { search: info });
const group = ref<string>('');
const opRef = ref();
+3 -3
View File
@@ -190,8 +190,8 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { toRef, onMounted, reactive, ref } from 'vue';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { onMounted, reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { deleteClam, handleClamScan, searchClam, updateClam, updateClamStatus } from '@/api/modules/toolbox';
@@ -218,7 +218,7 @@ const paginationConfig = reactive({
order: 'null',
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('toolbox/clam/index', { search: searchName });
const opRef = ref();
const dialogRef = ref();
+3 -3
View File
@@ -174,8 +174,8 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { toRef, onMounted, reactive, ref } from 'vue';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { onMounted, reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
import { deleteFtp, searchFtp, updateFtp, syncFtp, operateFtp, getFtpBase } from '@/api/modules/toolbox';
@@ -201,7 +201,7 @@ const paginationConfig = reactive({
order: 'null',
});
const searchName = ref();
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
useSearchPersistence('toolbox/ftp/index', { search: searchName });
const form = reactive({
isActive: true,
@@ -33,7 +33,7 @@
</template>
<script setup lang="ts">
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { Runtime } from '@/api/interface/runtime';
import { GetPHPExtensions, InstallPHPExtension, UnInstallPHPExtension } from '@/api/modules/runtime';
import i18n from '@/lang';
@@ -49,7 +49,7 @@ const supportExtensions = ref([]);
const loading = ref(false);
const taskLogRef = ref();
const searchName = ref('');
useSearchReset({ search: searchName }, { onRevisit: () => searchByName() });
useSearchPersistence('website/runtime/php/extension-management/index', { search: searchName });
const data = ref([]);
const handleClose = () => {
+2 -5
View File
@@ -221,7 +221,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { toRef, defineAsyncComponent, onMounted, reactive, ref } from 'vue';
import { deleteSSL, downloadFile, pushSSLToNode, searchSSL, updateSSL } from '@/api/modules/website';
import DnsAccount from './dns-account/index.vue';
@@ -287,10 +287,7 @@ const req = reactive({
orderBy: 'updated_at',
order: 'descending',
});
useSearchReset(
{ search: toRef(req, 'domain'), page: toRef(paginationConfig, 'currentPage') },
{ onRevisit: () => search() },
);
useSearchPersistence('website/ssl/index', { search: toRef(req, 'domain') });
const pushForm = ref({
id: 0,
pushNode: true,
@@ -59,7 +59,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import { Website } from '@/api/interface/website';
import { deleteTemplate, searchTemplates } from '@/api/modules/website';
import TemplateOperate from '@/views/website/template/operate/index.vue';
@@ -67,11 +67,12 @@ import OutputCreate from '@/views/website/template/output/create.vue';
import OutputList from '@/views/website/template/output/index.vue';
import { dateFormat } from '@/utils/date';
import i18n from '@/lang';
import { toRef, reactive, ref, onMounted } from 'vue';
import { reactive, ref, onMounted } from 'vue';
const loading = ref(false);
const data = ref<Website.Template[]>([]);
const searchName = ref('');
useSearchPersistence('website/template/index', { search: searchName });
const opRef = ref();
const operateRef = ref();
const outputCreateRef = ref();
@@ -90,7 +91,6 @@ const paginationConfig = reactive({
pageSize: 20,
total: 0,
});
useSearchReset({ search: searchName, page: toRef(paginationConfig, 'currentPage') }, { onRevisit: () => search() });
const buttons = [
{
+2 -5
View File
@@ -346,7 +346,7 @@
</template>
<script lang="ts" setup>
import { useSearchReset } from '@/composables/useSearchReset';
import { useSearchPersistence } from '@/composables/useSearchPersistence';
import Backups from '@/components/backup/index.vue';
import UploadDialog from '@/components/upload/index.vue';
import DefaultServer from '@/views/website/website/default/index.vue';
@@ -451,10 +451,7 @@ const pageState = usePageState(() => ({
}));
const paginationConfig = pageState.paginationConfig;
const req = pageState.req;
useSearchReset(
{ search: toRef(req, 'name'), page: toRef(paginationConfig, 'currentPage') },
{ onRevisit: () => search() },
);
useSearchPersistence('website/website/index', { search: toRef(req, 'name') });
const tableSort = pageState.tableSort;
const goRouter = async (key: string) => {