mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
200ddbfa28 | ||
|
|
f34418da6d | ||
|
|
7ff86c0ee3 | ||
|
|
5a58f44548 | ||
|
|
c75cb5d7ce | ||
|
|
2207147e7f | ||
|
|
b5604eab11 | ||
|
|
b498882708 | ||
|
|
381d71663c | ||
|
|
f7f4135193 | ||
|
|
dbf6d9e5fe |
@@ -28,7 +28,7 @@ func (b *BaseApi) SearchContainer(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := containerService.Page(req)
|
total, list, err := containerService.Page(c.Request.Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -249,9 +249,10 @@ func (b *BaseApi) ListContainerByImage(c *gin.Context) {
|
|||||||
// @Success 200 {object} dto.ContainerStatus
|
// @Success 200 {object} dto.ContainerStatus
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
|
// @Param containersOnly query boolean false "Only count containers"
|
||||||
// @Router /containers/status [get]
|
// @Router /containers/status [get]
|
||||||
func (b *BaseApi) LoadContainerStatus(c *gin.Context) {
|
func (b *BaseApi) LoadContainerStatus(c *gin.Context) {
|
||||||
data, err := containerService.LoadStatus()
|
data, err := containerService.LoadStatus(c.Request.Context(), c.Query("containersOnly") == "true")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -415,9 +416,14 @@ func (b *BaseApi) LoadResourceLimit(c *gin.Context) {
|
|||||||
// @Success 200 {array} dto.ContainerListStats
|
// @Success 200 {array} dto.ContainerListStats
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
|
// @Param ids query string false "Comma-separated container IDs; omitted selects all containers"
|
||||||
// @Router /containers/list/stats [get]
|
// @Router /containers/list/stats [get]
|
||||||
func (b *BaseApi) ContainerListStats(c *gin.Context) {
|
func (b *BaseApi) ContainerListStats(c *gin.Context) {
|
||||||
data, err := containerService.ContainerListStats()
|
var ids []string
|
||||||
|
if _, supplied := c.Request.URL.Query()["ids"]; supplied {
|
||||||
|
ids = strings.FieldsFunc(c.Query("ids"), func(r rune) bool { return r == ',' })
|
||||||
|
}
|
||||||
|
data, err := containerService.ContainerListStats(c.Request.Context(), ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@@ -964,3 +970,12 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) {
|
|||||||
|
|
||||||
containerService.StreamLogs(c, streamLog)
|
containerService.StreamLogs(c, streamLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) CleanNetworks(c *gin.Context) {
|
||||||
|
result, err := containerService.CleanNetworks()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, result)
|
||||||
|
}
|
||||||
|
|||||||
@@ -86,17 +86,38 @@ func (b *BaseApi) CheckHasCli(c *gin.Context) {
|
|||||||
|
|
||||||
// @Tags Database Redis
|
// @Tags Database Redis
|
||||||
// @Summary Install redis-cli
|
// @Summary Install redis-cli
|
||||||
// @Success 200
|
// @Accept json
|
||||||
|
// @Param request body dto.RedisCliInstall true "request"
|
||||||
|
// @Success 200 {object} dto.RedisCliStatus
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /databases/redis/install/cli [post]
|
// @Router /databases/redis/install/cli [post]
|
||||||
func (b *BaseApi) InstallCli(c *gin.Context) {
|
func (b *BaseApi) InstallCli(c *gin.Context) {
|
||||||
if err := redisService.InstallCli(); err != nil {
|
var req dto.RedisCliInstall
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := redisService.InstallCli(req)
|
||||||
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
helper.Success(c)
|
// @Tags Database Redis
|
||||||
|
// @Summary Load redis-cli installation status
|
||||||
|
// @Success 200 {object} dto.RedisCliStatus
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Security Timestamp
|
||||||
|
// @Router /databases/redis/cli/status [get]
|
||||||
|
func (b *BaseApi) LoadRedisCliStatus(c *gin.Context) {
|
||||||
|
data, err := redisService.LoadCliStatus()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Database Redis
|
// @Tags Database Redis
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ func (b *BaseApi) GetNodePackageRunScript(c *gin.Context) {
|
|||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Security Timestamp
|
// @Security Timestamp
|
||||||
// @Router /runtimes/operate [post]
|
// @Router /runtimes/operate [post]
|
||||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"操作运行环境 [id]","formatEN":"Operate runtime [id]"}
|
// @x-panel-log {"bodyKeys":["ID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ID","isList":false,"db":"runtimes","output_column":"name","output_value":"name"}],"formatZH":"操作运行环境 [name]","formatEN":"Operate runtime [name]"}
|
||||||
func (b *BaseApi) OperateRuntime(c *gin.Context) {
|
func (b *BaseApi) OperateRuntime(c *gin.Context) {
|
||||||
var req request.RuntimeOperate
|
var req request.RuntimeOperate
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
type NetworkCleanupReport struct {
|
||||||
|
Deleted []NetworkCleanupItem `json:"deleted"`
|
||||||
|
Skipped []NetworkCleanupItem `json:"skipped"`
|
||||||
|
Failed []NetworkCleanupItem `json:"failed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkCleanupItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkCleanupTask struct {
|
||||||
|
TaskID string `json:"taskID"`
|
||||||
|
}
|
||||||
@@ -22,6 +22,17 @@ type DBBaseInfo struct {
|
|||||||
Port int64 `json:"port"`
|
Port int64 `json:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RedisCliInstall struct {
|
||||||
|
TaskID string `json:"taskID" validate:"omitempty,uuid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedisCliStatus struct {
|
||||||
|
Installed bool `json:"installed"`
|
||||||
|
TaskID string `json:"taskID"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
ErrorMsg string `json:"errorMsg"`
|
||||||
|
}
|
||||||
|
|
||||||
// mysql
|
// mysql
|
||||||
type MysqlDBSearch struct {
|
type MysqlDBSearch struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ type ContainerService struct{}
|
|||||||
var containerLogAnsiRegex = regexp.MustCompile("\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>")
|
var containerLogAnsiRegex = regexp.MustCompile("\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>")
|
||||||
|
|
||||||
type IContainerService interface {
|
type IContainerService interface {
|
||||||
Page(req dto.PageContainer) (int64, interface{}, error)
|
Page(ctx context.Context, req dto.PageContainer) (int64, interface{}, error)
|
||||||
List() []dto.ContainerOptions
|
List() []dto.ContainerOptions
|
||||||
ListByImage(imageName string) []dto.ContainerOptions
|
ListByImage(imageName string) []dto.ContainerOptions
|
||||||
LoadStatus() (dto.ContainerStatus, error)
|
LoadStatus(ctx context.Context, containersOnly bool) (dto.ContainerStatus, error)
|
||||||
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
|
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
|
||||||
ListNetwork() ([]dto.Options, error)
|
ListNetwork() ([]dto.Options, error)
|
||||||
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
|
||||||
@@ -80,7 +80,7 @@ type IContainerService interface {
|
|||||||
ContainerUpdate(req dto.ContainerOperate) error
|
ContainerUpdate(req dto.ContainerOperate) error
|
||||||
ContainerUpgrade(req dto.ContainerUpgrade) error
|
ContainerUpgrade(req dto.ContainerUpgrade) error
|
||||||
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
||||||
ContainerListStats() ([]dto.ContainerListStats, error)
|
ContainerListStats(ctx context.Context, ids []string) ([]dto.ContainerListStats, error)
|
||||||
ContainerItemStats(ctx context.Context, req dto.OperationWithName) (dto.ContainerItemStats, error)
|
ContainerItemStats(ctx context.Context, req dto.OperationWithName) (dto.ContainerItemStats, error)
|
||||||
LoadResourceLimit() (*dto.ResourceLimit, error)
|
LoadResourceLimit() (*dto.ResourceLimit, error)
|
||||||
ContainerRename(req dto.ContainerRename) error
|
ContainerRename(req dto.ContainerRename) error
|
||||||
@@ -92,6 +92,7 @@ type IContainerService interface {
|
|||||||
|
|
||||||
Inspect(req dto.InspectReq) (string, error)
|
Inspect(req dto.InspectReq) (string, error)
|
||||||
DeleteNetwork(req dto.BatchDelete) error
|
DeleteNetwork(req dto.BatchDelete) error
|
||||||
|
CleanNetworks() (*dto.NetworkCleanupTask, error)
|
||||||
CreateNetwork(req dto.NetworkCreate) error
|
CreateNetwork(req dto.NetworkCreate) error
|
||||||
DeleteVolume(req dto.BatchDelete) error
|
DeleteVolume(req dto.BatchDelete) error
|
||||||
CreateVolume(req dto.VolumeCreate) error
|
CreateVolume(req dto.VolumeCreate) error
|
||||||
@@ -112,7 +113,9 @@ func NewIContainerService() IContainerService {
|
|||||||
return &ContainerService{}
|
return &ContainerService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
|
func (u *ContainerService) Page(ctx context.Context, req dto.PageContainer) (int64, interface{}, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
@@ -123,7 +126,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
|||||||
options.Filters = filters.NewArgs()
|
options.Filters = filters.NewArgs()
|
||||||
options.Filters.Add("label", req.Filters)
|
options.Filters.Add("label", req.Filters)
|
||||||
}
|
}
|
||||||
containers, err := client.ContainerList(context.Background(), options)
|
containers, err := client.ContainerList(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
@@ -206,15 +209,16 @@ func (u *ContainerService) ListByImage(imageName string) []dto.ContainerOptions
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
|
func (u *ContainerService) LoadStatus(ctx context.Context, containersOnly bool) (dto.ContainerStatus, error) {
|
||||||
var data dto.ContainerStatus
|
var data dto.ContainerStatus
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
c := context.Background()
|
c, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if !containersOnly {
|
||||||
images, _ := client.ImageList(c, image.ListOptions{All: true})
|
images, _ := client.ImageList(c, image.ListOptions{All: true})
|
||||||
data.ImageCount = len(images)
|
data.ImageCount = len(images)
|
||||||
repo, _ := imageRepoRepo.List()
|
repo, _ := imageRepoRepo.List()
|
||||||
@@ -225,8 +229,12 @@ func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
|
|||||||
data.NetworkCount = len(networks)
|
data.NetworkCount = len(networks)
|
||||||
volumes, _ := client.VolumeList(c, volume.ListOptions{})
|
volumes, _ := client.VolumeList(c, volume.ListOptions{})
|
||||||
data.VolumeCount = len(volumes.Volumes)
|
data.VolumeCount = len(volumes.Volumes)
|
||||||
data.ComposeCount = loadComposeCount(client)
|
data.ComposeCount = loadComposeCount(c, client)
|
||||||
containers, _ := client.ContainerList(c, container.ListOptions{All: true})
|
}
|
||||||
|
containers, err := client.ContainerList(c, container.ListOptions{All: true})
|
||||||
|
if err != nil {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
data.ContainerCount = len(containers)
|
data.ContainerCount = len(containers)
|
||||||
for _, item := range containers {
|
for _, item := range containers {
|
||||||
switch item.State {
|
switch item.State {
|
||||||
@@ -292,27 +300,67 @@ func (u *ContainerService) ContainerItemStats(ctx context.Context, req dto.Opera
|
|||||||
}
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
|
func (u *ContainerService) ContainerListStats(ctx context.Context, ids []string) ([]dto.ContainerListStats, error) {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
list, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
options := container.ListOptions{All: true}
|
||||||
|
if ids != nil {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return []dto.ContainerListStats{}, nil
|
||||||
|
}
|
||||||
|
options.Filters = filters.NewArgs()
|
||||||
|
for _, id := range ids {
|
||||||
|
options.Filters.Add("id", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list, err := client.ContainerList(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return collectContainerStats(ctx, list, func(ctx context.Context, id string) dto.ContainerListStats {
|
||||||
|
return loadCpuAndMem(ctx, client, id)
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A fixed worker pool bounds Docker stats requests, including for legacy callers
|
||||||
|
// that request all containers. Stopped containers do not need a stats sample.
|
||||||
|
func collectContainerStats(ctx context.Context, list []container.Summary, load func(context.Context, string) dto.ContainerListStats) []dto.ContainerListStats {
|
||||||
datas := make([]dto.ContainerListStats, len(list))
|
datas := make([]dto.ContainerListStats, len(list))
|
||||||
var wg sync.WaitGroup
|
for i, item := range list {
|
||||||
wg.Add(len(list))
|
datas[i].ContainerID = item.ID
|
||||||
for i := 0; i < len(list); i++ {
|
|
||||||
go func(index int, item container.Summary) {
|
|
||||||
datas[index] = loadCpuAndMem(client, item.ID)
|
|
||||||
wg.Done()
|
|
||||||
}(i, list[i])
|
|
||||||
}
|
}
|
||||||
|
jobs := make(chan int)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for worker := 0; worker < min(8, len(list)); worker++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for index := range jobs {
|
||||||
|
if ctx.Err() != nil || list[index].State != "running" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sampleCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||||
|
datas[index] = load(sampleCtx, list[index].ID)
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
dispatch:
|
||||||
|
for index := range list {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
break dispatch
|
||||||
|
case jobs <- index:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(jobs)
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return datas, nil
|
return datas
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||||
@@ -484,6 +532,10 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bool) error {
|
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bool) error {
|
||||||
|
return u.containerCreate(req, inThread, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) containerCreate(req dto.ContainerOperate, inThread bool, taskName string) error {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -497,7 +549,10 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
return buserr.New("ErrContainerName")
|
return buserr.New("ErrContainerName")
|
||||||
}
|
}
|
||||||
|
|
||||||
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
if taskName == "" {
|
||||||
|
taskName = task.GetTaskName(req.Name, task.TaskCreate, task.TaskScopeContainer)
|
||||||
|
}
|
||||||
|
taskItem, err := task.NewTask(taskName, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
unlock()
|
unlock()
|
||||||
_ = client.Close()
|
_ = client.Close()
|
||||||
@@ -558,6 +613,11 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
if inThread {
|
if inThread {
|
||||||
|
if err := taskItem.Prepare(); err != nil {
|
||||||
|
unlock()
|
||||||
|
_ = client.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
defer unlock()
|
defer unlock()
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
@@ -1715,11 +1775,11 @@ func selectImageRepo(imageName string, repos []model.ImageRepo) *model.ImageRepo
|
|||||||
return selected
|
return selected
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCpuAndMem(client *client.Client, containerItem string) dto.ContainerListStats {
|
func loadCpuAndMem(ctx context.Context, client *client.Client, containerItem string) dto.ContainerListStats {
|
||||||
data := dto.ContainerListStats{
|
data := dto.ContainerListStats{
|
||||||
ContainerID: containerItem,
|
ContainerID: containerItem,
|
||||||
}
|
}
|
||||||
res, err := client.ContainerStats(context.Background(), containerItem, false)
|
res, err := client.ContainerStats(ctx, containerItem, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
@@ -1942,11 +2002,11 @@ func transPortToStr(ports []container.Port) []string {
|
|||||||
return docker.SimplifyPorts(ports)
|
return docker.SimplifyPorts(ports)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadComposeCount(client *client.Client) int {
|
func loadComposeCount(ctx context.Context, client *client.Client) int {
|
||||||
options := container.ListOptions{All: true}
|
options := container.ListOptions{All: true}
|
||||||
options.Filters = filters.NewArgs()
|
options.Filters = filters.NewArgs()
|
||||||
options.Filters.Add("label", composeProjectLabel)
|
options.Filters.Add("label", composeProjectLabel)
|
||||||
list, err := client.ContainerList(context.Background(), options)
|
list, err := client.ContainerList(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
|
)
|
||||||
|
|
||||||
|
var networkCleanupMu sync.Mutex
|
||||||
|
|
||||||
|
func (u *ContainerService) CleanNetworks() (*dto.NetworkCleanupTask, error) {
|
||||||
|
taskItem, err := task.NewTaskWithOps(i18n.GetMsgByKey("Network"), task.TaskClean, task.TaskScopeContainer, "", 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
|
||||||
|
networkCleanupMu.Lock()
|
||||||
|
defer networkCleanupMu.Unlock()
|
||||||
|
if err := t.TaskCtx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cli, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
return executeNetworkCleanup(t, cli)
|
||||||
|
}, nil)
|
||||||
|
go func() {
|
||||||
|
if err := taskItem.Execute(); err != nil {
|
||||||
|
global.LOG.Errorf("network cleanup task %s failed: %v", taskItem.TaskID, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return &dto.NetworkCleanupTask{TaskID: taskItem.TaskID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeNetworkCleanup(t *task.Task, cli docker.NetworkCleanupClient) error {
|
||||||
|
t.Log(i18n.GetMsgByKey("PruneStart"))
|
||||||
|
report, err := docker.CleanUnusedNetworks(t.TaskCtx, cli, func(status string, item dto.NetworkCleanupItem) {
|
||||||
|
key := "NetworkCleanupDeleted"
|
||||||
|
if status == "skipped" || status == "failed" {
|
||||||
|
key = map[string]string{
|
||||||
|
"protected": "NetworkCleanupProtected",
|
||||||
|
"container_connected": "NetworkCleanupConnected",
|
||||||
|
"network_in_use": "NetworkCleanupConnected",
|
||||||
|
"unsupported_network": "NetworkCleanupUnsupported",
|
||||||
|
"already_removed": "NetworkCleanupGone",
|
||||||
|
"inspect_failed": "NetworkCleanupInspectFailed",
|
||||||
|
"remove_failed": "NetworkCleanupRemoveFailed",
|
||||||
|
}[item.Reason]
|
||||||
|
}
|
||||||
|
t.Log(i18n.GetMsgWithMap(key, map[string]interface{}{"name": item.Name, "id": item.ID}))
|
||||||
|
})
|
||||||
|
if report != nil {
|
||||||
|
t.Log(i18n.GetMsgWithMap("NetworkCleanupSummary", map[string]interface{}{
|
||||||
|
"deleted": len(report.Deleted), "skipped": len(report.Skipped), "failed": len(report.Failed),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(report.Failed) > 0 {
|
||||||
|
return fmt.Errorf("%s", i18n.GetMsgByKey("NetworkCleanupPartialFailure"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"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/i18n"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type networkTaskClient struct{ fail bool }
|
||||||
|
|
||||||
|
func (f networkTaskClient) NetworkList(context.Context, network.ListOptions) ([]network.Inspect, error) {
|
||||||
|
return []network.Inspect{{ID: "reserved", Name: "1panel-network", Scope: "local"}, {ID: "connected", Name: "busy", Scope: "local"}, {ID: "unused", Name: "free", Scope: "local"}, {ID: "race", Name: "race", Scope: "local"}}, nil
|
||||||
|
}
|
||||||
|
func (f networkTaskClient) ContainerList(context.Context, container.ListOptions) ([]container.Summary, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (f networkTaskClient) NetworkInspect(_ context.Context, id string, _ network.InspectOptions) (network.Inspect, error) {
|
||||||
|
n := network.Inspect{}
|
||||||
|
if id == "connected" {
|
||||||
|
n.Containers = map[string]network.EndpointResource{"container-id": {}}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
func (f networkTaskClient) NetworkRemove(_ context.Context, id string) error {
|
||||||
|
if id == "race" {
|
||||||
|
return errdefs.Conflict(errors.New("has active endpoints"))
|
||||||
|
}
|
||||||
|
if id != "unused" {
|
||||||
|
return errors.New("unexpected removal")
|
||||||
|
}
|
||||||
|
if f.fail {
|
||||||
|
return errors.New("remove failed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkCleanupTaskPersistsLogsAndStatus(t *testing.T) {
|
||||||
|
oldDB, oldTaskDB, oldDir, oldI18n := global.DB, global.TaskDB, global.Dir, global.I18n
|
||||||
|
t.Cleanup(func() { global.DB = oldDB; global.TaskDB = oldTaskDB; global.Dir = oldDir; global.I18n = oldI18n })
|
||||||
|
dir := t.TempDir()
|
||||||
|
db, err := gorm.Open(sqlite.Open(filepath.Join(dir, "tasks.db")), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sqlDB, err := db.DB()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer sqlDB.Close()
|
||||||
|
if err := db.AutoMigrate(&model.Task{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
global.DB = nil
|
||||||
|
global.TaskDB = db
|
||||||
|
global.Dir.TaskDir = dir
|
||||||
|
i18n.Init()
|
||||||
|
for _, fail := range []bool{false, true} {
|
||||||
|
name := "success"
|
||||||
|
wantStatus := constant.StatusSuccess
|
||||||
|
if fail {
|
||||||
|
name = "partial failure"
|
||||||
|
wantStatus = constant.StatusFailed
|
||||||
|
}
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
item, err := task.NewTaskWithOps("Network", task.TaskClean, task.TaskScopeContainer, "", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
item.AddSubTask("Clean", func(t *task.Task) error { return executeNetworkCleanup(t, networkTaskClient{fail: fail}) }, nil)
|
||||||
|
err = item.Execute()
|
||||||
|
if (err != nil) != fail {
|
||||||
|
t.Fatalf("unexpected execution error: %v", err)
|
||||||
|
}
|
||||||
|
var saved model.Task
|
||||||
|
if err := db.First(&saved, "id = ?", item.TaskID).Error; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if saved.Status != wantStatus {
|
||||||
|
t.Fatalf("status %s, want %s", saved.Status, wantStatus)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(saved.LogFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"[busy] (connected): containers connected", "[race] (race): containers connected", "[1panel-network] (reserved): reserved network", "Network cleanup finished:", "[TASK-END]"} {
|
||||||
|
if !strings.Contains(string(content), want) {
|
||||||
|
t.Fatalf("missing %q in log: %s", want, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
want := "Deleted network [free]"
|
||||||
|
if fail {
|
||||||
|
want = "Failed to remove network [free]"
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(content), want) {
|
||||||
|
t.Fatalf("missing %q", want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
@@ -23,6 +29,11 @@ import (
|
|||||||
|
|
||||||
type RedisService struct{}
|
type RedisService struct{}
|
||||||
|
|
||||||
|
const redisCliTaskName = "RedisCliEnable"
|
||||||
|
|
||||||
|
// The CLI container is shared by all remote Redis databases on this node.
|
||||||
|
var redisCliInstallMutex sync.Mutex
|
||||||
|
|
||||||
type IRedisService interface {
|
type IRedisService interface {
|
||||||
UpdateConf(req dto.RedisConfUpdate) error
|
UpdateConf(req dto.RedisConfUpdate) error
|
||||||
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
|
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
|
||||||
@@ -33,7 +44,8 @@ type IRedisService interface {
|
|||||||
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
|
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
|
||||||
|
|
||||||
CheckHasCli() bool
|
CheckHasCli() bool
|
||||||
InstallCli() error
|
InstallCli(req dto.RedisCliInstall) (*dto.RedisCliStatus, error)
|
||||||
|
LoadCliStatus() (*dto.RedisCliStatus, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIRedisService() IRedisService {
|
func NewIRedisService() IRedisService {
|
||||||
@@ -71,20 +83,62 @@ func (u *RedisService) CheckHasCli() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, item := range containerLists {
|
for _, item := range containerLists {
|
||||||
if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" {
|
if len(item.Names) > 0 && strings.TrimPrefix(item.Names[0], "/") == "1Panel-redis-cli-tools" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *RedisService) InstallCli() error {
|
func (u *RedisService) LoadCliStatus() (*dto.RedisCliStatus, error) {
|
||||||
|
result := &dto.RedisCliStatus{}
|
||||||
|
latest, err := taskRepo.GetFirst(repo.WithByName(redisCliTaskName), repo.WithByType(task.TaskScopeContainer), repo.WithOrderDesc("created_at"))
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
result.Installed = u.CheckHasCli()
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result.TaskID = latest.ID
|
||||||
|
result.Status = latest.Status
|
||||||
|
result.ErrorMsg = latest.ErrorMsg
|
||||||
|
result.Installed = u.CheckHasCli()
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RedisService) InstallCli(req dto.RedisCliInstall) (*dto.RedisCliStatus, error) {
|
||||||
|
if !redisCliInstallMutex.TryLock() {
|
||||||
|
return nil, buserr.New("TaskIsExecuting")
|
||||||
|
}
|
||||||
|
defer redisCliInstallMutex.Unlock()
|
||||||
|
status, err := u.LoadCliStatus()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if status.Status == constant.StatusExecuting || status.Installed {
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
if req.TaskID == "" {
|
||||||
|
req.TaskID = uuid.NewString()
|
||||||
|
}
|
||||||
|
// Never reuse an existing task ID: doing so would truncate its log.
|
||||||
|
if _, err := taskRepo.GetFirst(taskRepo.WithByID(req.TaskID)); !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, buserr.New("TaskIsExecuting")
|
||||||
|
}
|
||||||
item := dto.ContainerOperate{
|
item := dto.ContainerOperate{
|
||||||
|
TaskID: req.TaskID,
|
||||||
Name: "1Panel-redis-cli-tools",
|
Name: "1Panel-redis-cli-tools",
|
||||||
Image: "redis:7.4.4",
|
Image: "redis:7.4.4",
|
||||||
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
|
Networks: []dto.ContainerNetwork{{Network: "1panel-network"}},
|
||||||
}
|
}
|
||||||
return NewIContainerService().ContainerCreate(item, false)
|
if err := (&ContainerService{}).containerCreate(item, true, redisCliTaskName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &dto.RedisCliStatus{TaskID: req.TaskID, Status: constant.StatusExecuting}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -325,18 +324,16 @@ func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
client, err := docker.NewDockerClient()
|
|
||||||
if err != nil {
|
|
||||||
taskItem.Log("Failed to create Docker client: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
for _, itemPath := range req.Paths {
|
for _, itemPath := range req.Paths {
|
||||||
currentPath := itemPath
|
currentPath := itemPath
|
||||||
itemName := path.Base(currentPath)
|
itemName := path.Base(currentPath)
|
||||||
taskItem.AddSubTask(i18n.GetWithName("TaskImport", itemName), func(t *task.Task) error {
|
taskItem.AddSubTask(i18n.GetWithName("TaskImport", itemName), func(t *task.Task) error {
|
||||||
taskItem.Logf("----------------- %s -----------------", itemName)
|
taskItem.Logf("----------------- %s -----------------", itemName)
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
file, err := os.Open(currentPath)
|
file, err := os.Open(currentPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -347,14 +344,9 @@ func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
content, err := io.ReadAll(res.Body)
|
return consumeImageLoadResponse(res.Body, func(message string) {
|
||||||
if err != nil {
|
taskItem.Log(message)
|
||||||
return err
|
})
|
||||||
}
|
|
||||||
if strings.Contains(string(content), "Error") {
|
|
||||||
return errors.New(string(content))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
_ = taskItem.Execute()
|
_ = taskItem.Execute()
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Docker may report load failures in a successful HTTP response's JSON stream.
|
||||||
|
func consumeImageLoadResponse(reader io.Reader, log func(string)) error {
|
||||||
|
decoder := json.NewDecoder(reader)
|
||||||
|
for {
|
||||||
|
var message struct {
|
||||||
|
Stream string `json:"stream"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrorDetail struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
} `json:"errorDetail"`
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(&message); err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if message.Error != "" {
|
||||||
|
return errors.New(message.Error)
|
||||||
|
}
|
||||||
|
if message.ErrorDetail.Message != "" {
|
||||||
|
return errors.New(message.ErrorDetail.Message)
|
||||||
|
}
|
||||||
|
if text := strings.TrimSpace(message.Stream); text != "" {
|
||||||
|
log(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-2
@@ -154,7 +154,7 @@ func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, e
|
|||||||
logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log")
|
logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log")
|
||||||
logger := logrus.New()
|
logger := logrus.New()
|
||||||
logger.SetFormatter(&SimpleFormatter{})
|
logger.SetFormatter(&SimpleFormatter{})
|
||||||
logFile, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, constant.FilePerm)
|
logFile, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY|os.O_APPEND, constant.FilePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open log file: %w", err)
|
return nil, fmt.Errorf("failed to open log file: %w", err)
|
||||||
}
|
}
|
||||||
@@ -283,8 +283,18 @@ func (t *Task) updateTask(task *model.Task) {
|
|||||||
_ = t.taskRepo.Update(context.Background(), task)
|
_ = t.taskRepo.Update(context.Background(), task)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) Execute() error {
|
// Prepare makes a task visible before dispatching it to a background worker.
|
||||||
|
func (t *Task) Prepare() error {
|
||||||
if err := t.taskRepo.Save(context.Background(), t.Task); err != nil {
|
if err := t.taskRepo.Save(context.Background(), t.Task); err != nil {
|
||||||
|
_ = t.logFile.Close()
|
||||||
|
global.RemoveTaskCancel(t.TaskID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) Execute() error {
|
||||||
|
if err := t.Prepare(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -644,6 +644,13 @@
|
|||||||
"formatZH": "创建容器网络 name",
|
"formatZH": "创建容器网络 name",
|
||||||
"formatEN": "create container network [name]"
|
"formatEN": "create container network [name]"
|
||||||
},
|
},
|
||||||
|
"/containers/network/clean": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "清理未使用的容器网络",
|
||||||
|
"formatEN": "Clean unused container networks"
|
||||||
|
},
|
||||||
"/containers/network/del": {
|
"/containers/network/del": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"names"
|
"names"
|
||||||
@@ -4028,12 +4035,21 @@
|
|||||||
},
|
},
|
||||||
"/runtimes/operate": {
|
"/runtimes/operate": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"id"
|
"ID"
|
||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [
|
||||||
"formatZH": "操作运行环境 [id]",
|
{
|
||||||
"formatEN": "Operate runtime [id]"
|
"input_column": "id",
|
||||||
|
"input_value": "ID",
|
||||||
|
"isList": false,
|
||||||
|
"db": "runtimes",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "操作运行环境 [name]",
|
||||||
|
"formatEN": "Operate runtime [name]"
|
||||||
},
|
},
|
||||||
"/runtimes/php/config": {
|
"/runtimes/php/config": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
|
|||||||
@@ -731,3 +731,13 @@ FileTaskRename: 'Target filename: {{ .name }}'
|
|||||||
ErrFirewallBackendCleanupRequired: "The current backend {{ .current }} still contains 1Panel rules. Clean it up before switching to {{ .target }}."
|
ErrFirewallBackendCleanupRequired: "The current backend {{ .current }} still contains 1Panel rules. Clean it up before switching to {{ .target }}."
|
||||||
ErrDockerIPv4ForwardingDisabled: "IPv4 forwarding is disabled. Set net.ipv4.ip_forward=1 before using Docker's firewall backend."
|
ErrDockerIPv4ForwardingDisabled: "IPv4 forwarding is disabled. Set net.ipv4.ip_forward=1 before using Docker's firewall backend."
|
||||||
ErrFirewallRuleSavedApplyFailed: "The new rule configuration was saved, but applying it to the firewall failed. Retry by synchronizing: {{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "The new rule configuration was saved, but applying it to the firewall failed. Retry by synchronizing: {{ .detail }}"
|
||||||
|
|
||||||
|
NetworkCleanupDeleted: "Deleted network [{{ .name }}] ({{ .id }})"
|
||||||
|
NetworkCleanupProtected: "Skipped network [{{ .name }}] ({{ .id }}): reserved network"
|
||||||
|
NetworkCleanupConnected: "Skipped network [{{ .name }}] ({{ .id }}): containers connected or network in use; not deleted"
|
||||||
|
NetworkCleanupUnsupported: "Skipped network [{{ .name }}] ({{ .id }}): special network"
|
||||||
|
NetworkCleanupGone: "Skipped network [{{ .name }}] ({{ .id }}): already removed"
|
||||||
|
NetworkCleanupInspectFailed: "Failed to inspect network [{{ .name }}] ({{ .id }}); not deleted"
|
||||||
|
NetworkCleanupRemoveFailed: "Failed to remove network [{{ .name }}] ({{ .id }})"
|
||||||
|
NetworkCleanupSummary: "Network cleanup finished: deleted {{ .deleted }}, skipped {{ .skipped }}, failed {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "Some networks could not be cleaned; see the task log"
|
||||||
|
|||||||
@@ -731,3 +731,13 @@ FileTaskRename: '目標檔名:{{ .name }}'
|
|||||||
ErrFirewallBackendCleanupRequired: "目前後端 {{ .current }} 中仍有 1Panel 規則,請先清理後再切換至 {{ .target }}。"
|
ErrFirewallBackendCleanupRequired: "目前後端 {{ .current }} 中仍有 1Panel 規則,請先清理後再切換至 {{ .target }}。"
|
||||||
ErrDockerIPv4ForwardingDisabled: "IPv4 轉送尚未啟用,請先設定 net.ipv4.ip_forward=1,再使用 Docker 防火牆後端。"
|
ErrDockerIPv4ForwardingDisabled: "IPv4 轉送尚未啟用,請先設定 net.ipv4.ip_forward=1,再使用 Docker 防火牆後端。"
|
||||||
ErrFirewallRuleSavedApplyFailed: "新規則設定已儲存,但套用至防火牆失敗。可透過同步重試:{{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "新規則設定已儲存,但套用至防火牆失敗。可透過同步重試:{{ .detail }}"
|
||||||
|
|
||||||
|
NetworkCleanupDeleted: "網路 [{{ .name }}] ({{ .id }}) 已刪除"
|
||||||
|
NetworkCleanupProtected: "跳過網路 [{{ .name }}] ({{ .id }}):預設保留網路,未刪除"
|
||||||
|
NetworkCleanupConnected: "跳過網路 [{{ .name }}] ({{ .id }}):仍有容器連接或正在使用,未刪除"
|
||||||
|
NetworkCleanupUnsupported: "跳過網路 [{{ .name }}] ({{ .id }}):特殊網路,未刪除"
|
||||||
|
NetworkCleanupGone: "跳過網路 [{{ .name }}] ({{ .id }}):網路已不存在"
|
||||||
|
NetworkCleanupInspectFailed: "網路 [{{ .name }}] ({{ .id }}) 檢查失敗,未刪除"
|
||||||
|
NetworkCleanupRemoveFailed: "網路 [{{ .name }}] ({{ .id }}) 刪除失敗"
|
||||||
|
NetworkCleanupSummary: "網路清理完成:已刪除 {{ .deleted }},已跳過 {{ .skipped }},失敗 {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "部分網路清理失敗,請查看任務日誌"
|
||||||
|
|||||||
@@ -731,3 +731,13 @@ FileTaskRename: '目标文件名:{{ .name }}'
|
|||||||
ErrFirewallBackendCleanupRequired: "当前后端 {{ .current }} 中仍有 1Panel 规则,请先清理后再切换到 {{ .target }}。"
|
ErrFirewallBackendCleanupRequired: "当前后端 {{ .current }} 中仍有 1Panel 规则,请先清理后再切换到 {{ .target }}。"
|
||||||
ErrDockerIPv4ForwardingDisabled: "IPv4 转发未开启,请先设置 net.ipv4.ip_forward=1,再使用 Docker 防火墙后端。"
|
ErrDockerIPv4ForwardingDisabled: "IPv4 转发未开启,请先设置 net.ipv4.ip_forward=1,再使用 Docker 防火墙后端。"
|
||||||
ErrFirewallRuleSavedApplyFailed: "新规则配置已保存,但应用到防火墙失败。可通过同步重试:{{ .detail }}"
|
ErrFirewallRuleSavedApplyFailed: "新规则配置已保存,但应用到防火墙失败。可通过同步重试:{{ .detail }}"
|
||||||
|
|
||||||
|
NetworkCleanupDeleted: "网络 [{{ .name }}] ({{ .id }}) 已删除"
|
||||||
|
NetworkCleanupProtected: "跳过网络 [{{ .name }}] ({{ .id }}):默认保留网络,未删除"
|
||||||
|
NetworkCleanupConnected: "跳过网络 [{{ .name }}] ({{ .id }}):仍有容器连接或正在使用,未删除"
|
||||||
|
NetworkCleanupUnsupported: "跳过网络 [{{ .name }}] ({{ .id }}):特殊网络,未删除"
|
||||||
|
NetworkCleanupGone: "跳过网络 [{{ .name }}] ({{ .id }}):网络已不存在"
|
||||||
|
NetworkCleanupInspectFailed: "网络 [{{ .name }}] ({{ .id }}) 检查失败,未删除"
|
||||||
|
NetworkCleanupRemoveFailed: "网络 [{{ .name }}] ({{ .id }}) 删除失败"
|
||||||
|
NetworkCleanupSummary: "网络清理完成:已删除 {{ .deleted }},已跳过 {{ .skipped }},失败 {{ .failed }}"
|
||||||
|
NetworkCleanupPartialFailure: "部分网络清理失败,请查看任务日志"
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
|
|
||||||
baRouter.GET("/network", baseApi.ListNetwork)
|
baRouter.GET("/network", baseApi.ListNetwork)
|
||||||
baRouter.POST("/network/del", baseApi.DeleteNetwork)
|
baRouter.POST("/network/del", baseApi.DeleteNetwork)
|
||||||
|
baRouter.POST("/network/clean", baseApi.CleanNetworks)
|
||||||
baRouter.POST("/network/search", baseApi.SearchNetwork)
|
baRouter.POST("/network/search", baseApi.SearchNetwork)
|
||||||
baRouter.POST("/network", baseApi.CreateNetwork)
|
baRouter.POST("/network", baseApi.CreateNetwork)
|
||||||
baRouter.GET("/volume", baseApi.ListVolume)
|
baRouter.GET("/volume", baseApi.ListVolume)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus)
|
cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus)
|
||||||
cmdRouter.POST("/redis/conf", baseApi.LoadRedisConf)
|
cmdRouter.POST("/redis/conf", baseApi.LoadRedisConf)
|
||||||
cmdRouter.GET("/redis/check", baseApi.CheckHasCli)
|
cmdRouter.GET("/redis/check", baseApi.CheckHasCli)
|
||||||
|
cmdRouter.GET("/redis/cli/status", baseApi.LoadRedisCliStatus)
|
||||||
cmdRouter.POST("/redis/install/cli", baseApi.InstallCli)
|
cmdRouter.POST("/redis/install/cli", baseApi.InstallCli)
|
||||||
cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword)
|
cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword)
|
||||||
cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf)
|
cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf)
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetworkCleanupClient interface {
|
||||||
|
NetworkList(context.Context, network.ListOptions) ([]network.Inspect, error)
|
||||||
|
ContainerList(context.Context, container.ListOptions) ([]container.Summary, error)
|
||||||
|
NetworkInspect(context.Context, string, network.InspectOptions) (network.Inspect, error)
|
||||||
|
NetworkRemove(context.Context, string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func CleanUnusedNetworks(ctx context.Context, cli NetworkCleanupClient, onResult ...func(string, dto.NetworkCleanupItem)) (*dto.NetworkCleanupReport, error) {
|
||||||
|
networks, err := cli.NetworkList(ctx, network.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
report := &dto.NetworkCleanupReport{Deleted: []dto.NetworkCleanupItem{}, Skipped: []dto.NetworkCleanupItem{}, Failed: []dto.NetworkCleanupItem{}}
|
||||||
|
record := func(status string, item dto.NetworkCleanupItem) {
|
||||||
|
switch status {
|
||||||
|
case "deleted":
|
||||||
|
report.Deleted = append(report.Deleted, item)
|
||||||
|
case "skipped":
|
||||||
|
report.Skipped = append(report.Skipped, item)
|
||||||
|
case "failed":
|
||||||
|
report.Failed = append(report.Failed, item)
|
||||||
|
}
|
||||||
|
for _, notify := range onResult {
|
||||||
|
if notify != nil {
|
||||||
|
notify(status, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 report, err
|
||||||
|
}
|
||||||
|
item := dto.NetworkCleanupItem{ID: n.ID, Name: n.Name}
|
||||||
|
switch {
|
||||||
|
case n.Name == "none" || n.Name == "host" || n.Name == "bridge" || n.Name == "1panel-network":
|
||||||
|
item.Reason = "protected"
|
||||||
|
case n.Scope != "local" || n.Ingress || n.ConfigOnly:
|
||||||
|
item.Reason = "unsupported_network"
|
||||||
|
case used[n.Name] || used[n.ID]:
|
||||||
|
item.Reason = "container_connected"
|
||||||
|
}
|
||||||
|
if item.Reason != "" {
|
||||||
|
record("skipped", item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inspected, err := cli.NetworkInspect(ctx, n.ID, network.InspectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
item.Reason = "already_removed"
|
||||||
|
record("skipped", item)
|
||||||
|
} else {
|
||||||
|
item.Reason = "inspect_failed"
|
||||||
|
record("failed", item)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(inspected.Containers) > 0 {
|
||||||
|
item.Reason = "container_connected"
|
||||||
|
record("skipped", item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := cli.NetworkRemove(ctx, n.ID); err != nil {
|
||||||
|
switch {
|
||||||
|
case errdefs.IsNotFound(err):
|
||||||
|
item.Reason = "already_removed"
|
||||||
|
record("skipped", item)
|
||||||
|
case errdefs.IsConflict(err):
|
||||||
|
item.Reason = "network_in_use"
|
||||||
|
record("skipped", item)
|
||||||
|
default:
|
||||||
|
item.Reason = "remove_failed"
|
||||||
|
record("failed", item)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
record("deleted", item)
|
||||||
|
}
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cleanupFake struct {
|
||||||
|
networks []network.Inspect
|
||||||
|
containers []container.Summary
|
||||||
|
inspected map[string]network.Inspect
|
||||||
|
inspectErrors, removeErrors map[string]error
|
||||||
|
listError error
|
||||||
|
removed []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *cleanupFake) NetworkList(context.Context, network.ListOptions) ([]network.Inspect, error) {
|
||||||
|
return f.networks, nil
|
||||||
|
}
|
||||||
|
func (f *cleanupFake) ContainerList(_ context.Context, o container.ListOptions) ([]container.Summary, error) {
|
||||||
|
if !o.All {
|
||||||
|
panic("must include stopped containers")
|
||||||
|
}
|
||||||
|
return f.containers, f.listError
|
||||||
|
}
|
||||||
|
func (f *cleanupFake) NetworkInspect(_ context.Context, id string, _ network.InspectOptions) (network.Inspect, error) {
|
||||||
|
return f.inspected[id], f.inspectErrors[id]
|
||||||
|
}
|
||||||
|
func (f *cleanupFake) NetworkRemove(_ context.Context, id string) error {
|
||||||
|
f.removed = append(f.removed, id)
|
||||||
|
return f.removeErrors[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkCleanupProtectsAndReports(t *testing.T) {
|
||||||
|
f := &cleanupFake{inspected: map[string]network.Inspect{"attached": {Containers: map[string]network.EndpointResource{"container": {}}}}, inspectErrors: map[string]error{"unknown": errors.New("inspect failure")}, removeErrors: map[string]error{"race": errdefs.Conflict(errors.New("has active endpoints")), "gone": errdefs.NotFound(errors.New("gone")), "failed": errors.New("denied")}}
|
||||||
|
for _, name := range []string{"none", "host", "bridge", "1panel-network", "configured", "attached", "stopped", "free", "unknown", "race", "gone", "failed"} {
|
||||||
|
f.networks = append(f.networks, network.Inspect{ID: name, Name: name, Scope: "local"})
|
||||||
|
}
|
||||||
|
// Compose ownership labels do not protect an otherwise unused network.
|
||||||
|
for i := range f.networks {
|
||||||
|
if f.networks[i].Name == "configured" {
|
||||||
|
f.networks[i].Labels = map[string]string{"com.docker.compose.project": "demo", "com.docker.compose.network": "default"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.containers = []container.Summary{{State: "exited", NetworkSettings: &container.NetworkSettingsSummary{Networks: map[string]*network.EndpointSettings{"stopped": {NetworkID: "stopped"}}}}}
|
||||||
|
report, err := CleanUnusedNetworks(context.Background(), f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(f.removed, []string{"configured", "failed", "free", "gone", "race"}) {
|
||||||
|
t.Fatal(f.removed)
|
||||||
|
}
|
||||||
|
if len(report.Deleted) != 2 || report.Deleted[0].Name != "configured" || report.Deleted[1].Name != "free" || len(report.Failed) != 2 || len(report.Skipped) != 8 {
|
||||||
|
t.Fatalf("%+v", report)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkCleanupDiscoveryFailureDeletesNothing(t *testing.T) {
|
||||||
|
f := &cleanupFake{networks: []network.Inspect{{ID: "free", Name: "free", Scope: "local"}}, listError: errors.New("cannot list containers")}
|
||||||
|
if _, err := CleanUnusedNetworks(context.Background(), f); err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
if len(f.removed) > 0 {
|
||||||
|
t.Fatal(f.removed)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15010,6 +15010,30 @@ const docTemplate = `{
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/databases/redis/cli/status": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.RedisCliStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Timestamp": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": "Load redis-cli installation status",
|
||||||
|
"tags": [
|
||||||
|
"Database Redis"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/databases/redis/conf": {
|
"/databases/redis/conf": {
|
||||||
"post": {
|
"post": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@@ -15092,9 +15116,26 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/databases/redis/install/cli": {
|
"/databases/redis/install/cli": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"in": "body",
|
||||||
|
"name": "request",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.RedisCliInstall"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.RedisCliStatus"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
@@ -39731,6 +39772,31 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"dto.RedisCliInstall": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"taskID": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.RedisCliStatus": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"errorMsg": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"installed": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"taskID": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.RedisConf": {
|
"dto.RedisConf": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"containerName": {
|
"containerName": {
|
||||||
|
|||||||
@@ -15006,6 +15006,30 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/databases/redis/cli/status": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.RedisCliStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Timestamp": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": "Load redis-cli installation status",
|
||||||
|
"tags": [
|
||||||
|
"Database Redis"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/databases/redis/conf": {
|
"/databases/redis/conf": {
|
||||||
"post": {
|
"post": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@@ -15088,9 +15112,26 @@
|
|||||||
},
|
},
|
||||||
"/databases/redis/install/cli": {
|
"/databases/redis/install/cli": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"in": "body",
|
||||||
|
"name": "request",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.RedisCliInstall"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK"
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.RedisCliStatus"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
@@ -39727,6 +39768,31 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"dto.RedisCliInstall": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"taskID": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.RedisCliStatus": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"errorMsg": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"installed": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"taskID": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.RedisConf": {
|
"dto.RedisConf": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"containerName": {
|
"containerName": {
|
||||||
|
|||||||
@@ -644,6 +644,13 @@
|
|||||||
"formatZH": "创建容器网络 name",
|
"formatZH": "创建容器网络 name",
|
||||||
"formatEN": "create container network [name]"
|
"formatEN": "create container network [name]"
|
||||||
},
|
},
|
||||||
|
"/containers/network/clean": {
|
||||||
|
"bodyKeys": [],
|
||||||
|
"paramKeys": [],
|
||||||
|
"beforeFunctions": [],
|
||||||
|
"formatZH": "清理未使用的容器网络",
|
||||||
|
"formatEN": "Clean unused container networks"
|
||||||
|
},
|
||||||
"/containers/network/del": {
|
"/containers/network/del": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"names"
|
"names"
|
||||||
@@ -4028,12 +4035,21 @@
|
|||||||
},
|
},
|
||||||
"/runtimes/operate": {
|
"/runtimes/operate": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
"id"
|
"ID"
|
||||||
],
|
],
|
||||||
"paramKeys": [],
|
"paramKeys": [],
|
||||||
"beforeFunctions": [],
|
"beforeFunctions": [
|
||||||
"formatZH": "操作运行环境 [id]",
|
{
|
||||||
"formatEN": "Operate runtime [id]"
|
"input_column": "id",
|
||||||
|
"input_value": "ID",
|
||||||
|
"isList": false,
|
||||||
|
"db": "runtimes",
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatZH": "操作运行环境 [name]",
|
||||||
|
"formatEN": "Operate runtime [name]"
|
||||||
},
|
},
|
||||||
"/runtimes/php/config": {
|
"/runtimes/php/config": {
|
||||||
"bodyKeys": [
|
"bodyKeys": [
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# 批量清理未使用的容器网络
|
||||||
|
|
||||||
|
`POST /api/v2/containers/network/clean`,使用现有 1Panel 认证,不需要请求体。
|
||||||
|
这是异步接口,立即返回 `data.taskID`。实际结果在任务中心和任务日志查看,HTTP 请求结束不会取消任务。
|
||||||
|
|
||||||
|
规则:
|
||||||
|
|
||||||
|
- 永远保留 `none`、`host`、`bridge`、`1panel-network`。
|
||||||
|
- 保留所有容器(包括停止容器)引用的网络,删除前再通过 NetworkInspect 检查连接。
|
||||||
|
- 不检查 Compose 文件或应用配置引用;仅被这些配置引用、没有容器连接的普通网络也会被删除。
|
||||||
|
- 当前仅清理本地普通网络,跳过 Swarm、ingress 和 config-only 网络。
|
||||||
|
- Docker 删除时发现新连接会拒绝删除;不会强制断开容器。
|
||||||
|
|
||||||
|
成功响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"code":200,"data":{"taskID":"服务端生成的任务 UUID"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
任务类型为 Container,操作为 TaskClean。网络页“清理网络”按钮调用此接口,拿到 taskID 后立即打开任务日志,并刷新任务中心。
|
||||||
|
每个网络处理完成后写入名称、ID 和结果;有容器连接或 Docker 返回占用冲突时,明确记录“未删除”。最后记录已删除、跳过和失败数量。
|
||||||
|
跳过在用网络属于正常结果;检查/删除失败会将任务标记为失败,已删除的网络不回滚。
|
||||||
|
|
||||||
|
日志调用链:
|
||||||
|
|
||||||
|
- `agent/app/service/container_network_cleanup.go`:NewTaskWithOps 创建任务,AddSubTask 注册清理步骤,Execute 执行任务,t.Log 写逐项日志。
|
||||||
|
- `agent/app/task/task.go`:任务持久化,维护状态,写入开始、结束标记;日志文件为 `<global.Dir.TaskDir>/Container/<taskID>.log`。
|
||||||
|
- `frontend/src/views/container/network/index.vue`:请求成功后调用 openTaskLog(taskID)。
|
||||||
|
- `frontend/src/components/log/task/index.vue`:openWithTaskID 打开日志弹窗。
|
||||||
|
- `frontend/src/components/log/file/index.vue`:通过 `/logs/tasks/read` 按行读取任务日志。
|
||||||
|
|
||||||
|
范围:不检查 Compose 文件或应用配置引用;仅清理本地普通网络。原 `/containers/prune` 接口仍保持原逻辑。
|
||||||
|
测试使用假 Docker 客户端和临时任务数据库,不执行真实网络删除。
|
||||||
Generated
+10
-173
@@ -126,7 +126,6 @@
|
|||||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.7",
|
"@babel/code-frame": "^7.29.7",
|
||||||
"@babel/generator": "^7.29.7",
|
"@babel/generator": "^7.29.7",
|
||||||
@@ -1014,7 +1013,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1031,7 +1029,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1048,7 +1045,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1065,7 +1061,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1082,7 +1077,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1099,7 +1093,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1116,7 +1109,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1133,7 +1125,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1150,7 +1141,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1167,7 +1157,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1184,7 +1173,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1201,7 +1189,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1218,7 +1205,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1235,7 +1221,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1252,7 +1237,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1269,7 +1253,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1286,7 +1269,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1303,7 +1285,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1320,7 +1301,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1337,7 +1317,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1354,7 +1333,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1371,7 +1349,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1388,7 +1365,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1405,7 +1381,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1422,7 +1397,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1439,7 +1413,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1773,18 +1746,6 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/source-map": {
|
|
||||||
"version": "0.3.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
|
||||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
|
||||||
"@jridgewell/trace-mapping": "^0.3.25"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
@@ -2013,7 +1974,6 @@
|
|||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||||
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -2053,7 +2013,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2074,7 +2033,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2095,7 +2053,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2116,7 +2073,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2137,7 +2093,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2158,7 +2113,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2179,7 +2133,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2200,7 +2153,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2221,7 +2173,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2242,7 +2193,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2263,7 +2213,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2284,7 +2233,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2305,7 +2253,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2350,7 +2297,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2367,7 +2313,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2384,7 +2329,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2401,7 +2345,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2418,7 +2361,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2435,7 +2377,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2452,7 +2393,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2469,7 +2409,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2486,7 +2425,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2503,7 +2441,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2520,7 +2457,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2537,7 +2473,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2554,7 +2489,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2571,7 +2505,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2984,7 +2917,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
||||||
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/lodash": "*"
|
"@types/lodash": "*"
|
||||||
}
|
}
|
||||||
@@ -3016,9 +2948,8 @@
|
|||||||
"version": "26.4.0",
|
"version": "26.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz",
|
||||||
"integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==",
|
"integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~8.3.0"
|
"undici-types": "~8.3.0"
|
||||||
}
|
}
|
||||||
@@ -3321,7 +3252,6 @@
|
|||||||
"integrity": "sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==",
|
"integrity": "sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.68.0",
|
"@typescript-eslint/scope-manager": "8.68.0",
|
||||||
"@typescript-eslint/types": "8.68.0",
|
"@typescript-eslint/types": "8.68.0",
|
||||||
@@ -4005,7 +3935,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.4.0.tgz",
|
||||||
"integrity": "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==",
|
"integrity": "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/web-bluetooth": "^0.0.21",
|
"@types/web-bluetooth": "^0.0.21",
|
||||||
"@vueuse/metadata": "14.4.0",
|
"@vueuse/metadata": "14.4.0",
|
||||||
@@ -4052,15 +3981,13 @@
|
|||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.18.0",
|
"version": "8.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
|
||||||
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -4317,7 +4244,6 @@
|
|||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4347,7 +4273,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.11.12",
|
"baseline-browser-mapping": "^2.11.12",
|
||||||
"caniuse-lite": "^1.0.30001809",
|
"caniuse-lite": "^1.0.30001809",
|
||||||
@@ -4688,7 +4613,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||||
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.0.0",
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
"@codemirror/commands": "^6.0.0",
|
"@codemirror/commands": "^6.0.0",
|
||||||
@@ -5373,7 +5297,6 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -5766,7 +5689,6 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
@@ -5828,7 +5750,6 @@
|
|||||||
"integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==",
|
"integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
@@ -5888,7 +5809,6 @@
|
|||||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"eslint-config-prettier": "bin/cli.js"
|
"eslint-config-prettier": "bin/cli.js"
|
||||||
},
|
},
|
||||||
@@ -6240,7 +6160,6 @@
|
|||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6342,7 +6261,6 @@
|
|||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -6822,7 +6740,7 @@
|
|||||||
"version": "5.1.9",
|
"version": "5.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz",
|
||||||
"integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==",
|
"integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/imurmurhash": {
|
"node_modules/imurmurhash": {
|
||||||
@@ -6895,7 +6813,7 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -6921,7 +6839,7 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
@@ -6966,7 +6884,6 @@
|
|||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -7052,7 +6969,7 @@
|
|||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
|
||||||
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
|
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
@@ -7743,15 +7660,13 @@
|
|||||||
"version": "4.18.1",
|
"version": "4.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/lodash-es": {
|
"node_modules/lodash-es": {
|
||||||
"version": "4.18.1",
|
"version": "4.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
|
||||||
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
|
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/lodash-unified": {
|
"node_modules/lodash-unified": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
@@ -7955,7 +7870,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1",
|
"argparse": "^2.0.1",
|
||||||
"entities": "^4.5.0",
|
"entities": "^4.5.0",
|
||||||
@@ -8253,7 +8167,6 @@
|
|||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -8441,7 +8354,6 @@
|
|||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
@@ -8697,7 +8609,6 @@
|
|||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -8722,7 +8633,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz",
|
||||||
"integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
|
"integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/devtools-api": "^7.7.7"
|
"@vue/devtools-api": "^7.7.7"
|
||||||
},
|
},
|
||||||
@@ -8803,7 +8713,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.17",
|
"nanoid": "^3.3.17",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -8915,7 +8824,6 @@
|
|||||||
"integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
|
"integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@@ -9284,7 +9192,6 @@
|
|||||||
"integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==",
|
"integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oxc-project/types": "=0.143.0",
|
"@oxc-project/types": "=0.143.0",
|
||||||
"@rolldown/pluginutils": "^1.0.0"
|
"@rolldown/pluginutils": "^1.0.0"
|
||||||
@@ -9394,9 +9301,8 @@
|
|||||||
"version": "1.103.1",
|
"version": "1.103.1",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.103.1.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.103.1.tgz",
|
||||||
"integrity": "sha512-9icZURbP51S6S0QGoyaeqk9uB06GNWxsFYWfH5RgpFgqK5FA8tJcM3AdVxrZEVJ7dz+L87nG95gBKf4VuaMHGw==",
|
"integrity": "sha512-9icZURbP51S6S0QGoyaeqk9uB06GNWxsFYWfH5RgpFgqK5FA8tJcM3AdVxrZEVJ7dz+L87nG95gBKf4VuaMHGw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^5.0.0",
|
"chokidar": "^5.0.0",
|
||||||
"immutable": "^5.1.5",
|
"immutable": "^5.1.5",
|
||||||
@@ -9622,29 +9528,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-support": {
|
|
||||||
"version": "0.5.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
|
||||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"buffer-from": "^1.0.0",
|
|
||||||
"source-map": "^0.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/source-map-support/node_modules/source-map": {
|
|
||||||
"version": "0.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/spdx-correct": {
|
"node_modules/spdx-correct": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
|
||||||
@@ -9989,34 +9872,6 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser": {
|
|
||||||
"version": "5.44.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz",
|
|
||||||
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
|
||||||
"acorn": "^8.15.0",
|
|
||||||
"commander": "^2.20.0",
|
|
||||||
"source-map-support": "~0.5.20"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"terser": "bin/terser"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/terser/node_modules/commander": {
|
|
||||||
"version": "2.20.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/text-extensions": {
|
"node_modules/text-extensions": {
|
||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz",
|
||||||
@@ -10098,7 +9953,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -10110,7 +9964,6 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -10175,7 +10028,6 @@
|
|||||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -10214,7 +10066,7 @@
|
|||||||
"version": "8.3.0",
|
"version": "8.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
||||||
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unimport": {
|
"node_modules/unimport": {
|
||||||
@@ -10604,7 +10456,6 @@
|
|||||||
"integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==",
|
"integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lightningcss": "^1.33.0",
|
"lightningcss": "^1.33.0",
|
||||||
"picomatch": "^4.0.5",
|
"picomatch": "^4.0.5",
|
||||||
@@ -10879,7 +10730,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -10900,7 +10750,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -10921,7 +10770,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -10942,7 +10790,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -10963,7 +10810,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -10984,7 +10830,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11005,7 +10850,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11026,7 +10870,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11047,7 +10890,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11068,7 +10910,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11089,7 +10930,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11128,7 +10968,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz",
|
||||||
"integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==",
|
"integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.42",
|
"@vue/compiler-dom": "3.5.42",
|
||||||
"@vue/compiler-sfc": "3.5.42",
|
"@vue/compiler-sfc": "3.5.42",
|
||||||
@@ -11182,7 +11021,6 @@
|
|||||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
@@ -11209,7 +11047,6 @@
|
|||||||
"integrity": "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==",
|
"integrity": "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.0",
|
||||||
"eslint-scope": "^8.2.0 || ^9.0.0",
|
"eslint-scope": "^8.2.0 || ^9.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { ReqPage } from '.';
|
import { ReqPage } from '.';
|
||||||
|
|
||||||
export namespace Database {
|
export namespace Database {
|
||||||
|
export interface RedisCliStatus {
|
||||||
|
installed: boolean;
|
||||||
|
taskID: string;
|
||||||
|
status: string;
|
||||||
|
errorMsg: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SearchDBWithPage {
|
export interface SearchDBWithPage {
|
||||||
info: string;
|
info: string;
|
||||||
database: string;
|
database: string;
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ import { ResPage, SearchWithPage } from '../interface';
|
|||||||
import { Container } from '../interface/container';
|
import { Container } from '../interface/container';
|
||||||
import { TimeoutEnum } from '@/enums/http-enum';
|
import { TimeoutEnum } from '@/enums/http-enum';
|
||||||
|
|
||||||
export const searchContainer = (params: Container.ContainerSearch, currentNode?: string) => {
|
export const searchContainer = (params: Container.ContainerSearch, currentNode?: string, signal?: AbortSignal) => {
|
||||||
return http.post<ResPage<Container.ContainerInfo>>(
|
return http.postWithConfig<ResPage<Container.ContainerInfo>>(`/containers/search`, params, {
|
||||||
`/containers/search`,
|
timeout: TimeoutEnum.T_40S,
|
||||||
params,
|
headers: currentNode ? { CurrentNode: currentNode } : undefined,
|
||||||
TimeoutEnum.T_40S,
|
signal,
|
||||||
currentNode ? { CurrentNode: currentNode } : undefined,
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export const listContainer = () => {
|
export const listContainer = () => {
|
||||||
return http.post<Array<Container.ContainerOption>>(`/containers/list`, {});
|
return http.post<Array<Container.ContainerOption>>(`/containers/list`, {});
|
||||||
@@ -44,12 +43,11 @@ export const downloadContainerFile = (params: { containerID: string; path: strin
|
|||||||
timeout: TimeoutEnum.T_40S,
|
timeout: TimeoutEnum.T_40S,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
export const loadContainerStatus = (currentNode?: string) => {
|
export const loadContainerStatus = (currentNode?: string, containersOnly = false, signal?: AbortSignal) => {
|
||||||
return http.get<Container.ContainerStatus>(
|
return http.get<Container.ContainerStatus>(`/containers/status`, containersOnly ? { containersOnly: true } : {}, {
|
||||||
`/containers/status`,
|
headers: currentNode ? { CurrentNode: currentNode } : undefined,
|
||||||
{},
|
signal,
|
||||||
currentNode ? { headers: { CurrentNode: currentNode } } : {},
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export const loadResourceLimit = () => {
|
export const loadResourceLimit = () => {
|
||||||
return http.get<Container.ResourceLimit>(`/containers/limit`);
|
return http.get<Container.ResourceLimit>(`/containers/limit`);
|
||||||
@@ -90,12 +88,11 @@ export const containerItemStats = (containerID: string, currentNode?: string) =>
|
|||||||
currentNode ? { CurrentNode: currentNode } : undefined,
|
currentNode ? { CurrentNode: currentNode } : undefined,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export const containerListStats = (currentNode?: string) => {
|
export const containerListStats = (currentNode?: string, ids?: string[], signal?: AbortSignal) => {
|
||||||
return http.get<Array<Container.ContainerListStats>>(
|
return http.get<Array<Container.ContainerListStats>>(`/containers/list/stats`, ids ? { ids: ids.join(',') } : {}, {
|
||||||
`/containers/list/stats`,
|
headers: currentNode ? { CurrentNode: currentNode } : undefined,
|
||||||
{},
|
signal,
|
||||||
currentNode ? { headers: { CurrentNode: currentNode } } : {},
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export const containerStats = (id: string) => {
|
export const containerStats = (id: string) => {
|
||||||
return http.get<Container.ContainerStats>(`/containers/stats/${id}`);
|
return http.get<Container.ContainerStats>(`/containers/stats/${id}`);
|
||||||
@@ -144,8 +141,13 @@ export const imagePull = (params: Container.ImagePull) => {
|
|||||||
export const imagePush = (params: Container.ImagePush) => {
|
export const imagePush = (params: Container.ImagePush) => {
|
||||||
return http.post<string>(`/containers/image/push`, params);
|
return http.post<string>(`/containers/image/push`, params);
|
||||||
};
|
};
|
||||||
export const imageLoad = (params: Container.ImageLoad) => {
|
export const imageLoad = (params: Container.ImageLoad, currentNode?: string) => {
|
||||||
return http.post(`/containers/image/load`, params, TimeoutEnum.T_10M);
|
return http.post(
|
||||||
|
`/containers/image/load`,
|
||||||
|
params,
|
||||||
|
TimeoutEnum.T_10M,
|
||||||
|
currentNode ? { CurrentNode: currentNode } : undefined,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const imageSave = (params: Container.ImageSave) => {
|
export const imageSave = (params: Container.ImageSave) => {
|
||||||
return http.post(`/containers/image/save`, params, TimeoutEnum.T_10M);
|
return http.post(`/containers/image/save`, params, TimeoutEnum.T_10M);
|
||||||
@@ -164,6 +166,9 @@ export const searchNetwork = (params: SearchWithPage) => {
|
|||||||
export const listNetwork = () => {
|
export const listNetwork = () => {
|
||||||
return http.get<Array<Container.Options>>(`/containers/network`);
|
return http.get<Array<Container.Options>>(`/containers/network`);
|
||||||
};
|
};
|
||||||
|
export const cleanNetworks = () => {
|
||||||
|
return http.post<{ taskID: string }>(`/containers/network/clean`, {});
|
||||||
|
};
|
||||||
export const deleteNetwork = (params: Container.BatchDelete) => {
|
export const deleteNetwork = (params: Container.BatchDelete) => {
|
||||||
return http.post(`/containers/network/del`, params);
|
return http.post(`/containers/network/del`, params);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -193,8 +193,13 @@ export const redisPersistenceConf = (type: string, database: string) => {
|
|||||||
export const checkRedisCli = () => {
|
export const checkRedisCli = () => {
|
||||||
return http.get<boolean>(`/databases/redis/check`);
|
return http.get<boolean>(`/databases/redis/check`);
|
||||||
};
|
};
|
||||||
export const installRedisCli = () => {
|
export const loadRedisCliStatus = (node: string) => {
|
||||||
return http.post(`/databases/redis/install/cli`, {}, TimeoutEnum.T_5M);
|
return http.get<Database.RedisCliStatus>(`/databases/redis/cli/status?operateNode=${encodeURIComponent(node)}`);
|
||||||
|
};
|
||||||
|
export const installRedisCli = (taskID: string, node: string) => {
|
||||||
|
return http.post<Database.RedisCliStatus>(`/databases/redis/install/cli?operateNode=${encodeURIComponent(node)}`, {
|
||||||
|
taskID,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
export const changeRedisPassword = (database: string, password: string) => {
|
export const changeRedisPassword = (database: string, password: string) => {
|
||||||
if (password) {
|
if (password) {
|
||||||
|
|||||||
@@ -16,10 +16,12 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
defineOptions({ name: 'TableSearch' });
|
defineOptions({ name: 'TableSearch' });
|
||||||
|
|
||||||
const emit = defineEmits(['search', 'update:searchName']);
|
const emit = defineEmits(['search', 'update:searchName']);
|
||||||
const searchInfo = ref();
|
const searchInfo = ref();
|
||||||
|
const route = useRoute();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
placeholder: String,
|
placeholder: String,
|
||||||
disabled: {
|
disabled: {
|
||||||
@@ -42,6 +44,16 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Also discard text typed without pressing Enter when leaving a cached tab.
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
() => {
|
||||||
|
searchInfo.value = '';
|
||||||
|
emit('update:searchName', '');
|
||||||
|
},
|
||||||
|
{ flush: 'sync' },
|
||||||
|
);
|
||||||
|
|
||||||
const search = () => {
|
const search = () => {
|
||||||
emit('update:searchName', searchInfo.value);
|
emit('update:searchName', searchInfo.value);
|
||||||
emit('search');
|
emit('search');
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<fu-table
|
<fu-table
|
||||||
v-if="currentViewMode === 'table'"
|
v-if="currentViewMode === 'table'"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
|
:data="props.data"
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
@select="handleSelect"
|
@select="handleSelect"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
@@ -166,16 +167,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, useAttrs } from 'vue';
|
import { computed, onActivated, onMounted, ref, useAttrs } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||||
import { flattenVNodes, type FuTableOperationButton } from '@/components/table/shared';
|
import { flattenVNodes, type FuTableOperationButton } from '@/components/table/shared';
|
||||||
import { useCardColumns } from './useCardColumns';
|
import { useCardColumns } from './useCardColumns';
|
||||||
import { useContextMenu } from './useContextMenu';
|
import { useContextMenu } from './useContextMenu';
|
||||||
import { useResponsivePagination } from './useResponsivePagination';
|
import { useResponsivePagination } from './useResponsivePagination';
|
||||||
import { useTableSelection } from './useTableSelection';
|
import { useTableSelection } from './useTableSelection';
|
||||||
|
import { useTablePageState } from './useTablePageState';
|
||||||
const slots = useSlots();
|
const slots = useSlots();
|
||||||
const attrs = useAttrs();
|
const attrs = useAttrs();
|
||||||
const { isMobile, openMenuTabs } = useGlobalStore();
|
const route = useRoute();
|
||||||
|
const { isMobile, openMenuTabs, currentNode } = useGlobalStore();
|
||||||
|
|
||||||
defineOptions({ name: 'ComplexTable' });
|
defineOptions({ name: 'ComplexTable' });
|
||||||
export type DropdownProps = FuTableOperationButton;
|
export type DropdownProps = FuTableOperationButton;
|
||||||
@@ -184,6 +188,8 @@ type ViewMode = 'table' | 'card';
|
|||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
header: String,
|
header: String,
|
||||||
|
data: { type: Array as PropType<any[]>, default: () => [] },
|
||||||
|
selectionContext: { type: [String, Number, Array, Object, Function], default: undefined },
|
||||||
paginationConfig: {
|
paginationConfig: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -239,7 +245,7 @@ const selectionColumn = computed(() =>
|
|||||||
columnNodes.value.find((column) => (column.props as Record<string, any> | null)?.type === 'selection'),
|
columnNodes.value.find((column) => (column.props as Record<string, any> | null)?.type === 'selection'),
|
||||||
);
|
);
|
||||||
const tableData = computed(() => {
|
const tableData = computed(() => {
|
||||||
const data = attrs.data;
|
const data = props.data;
|
||||||
return Array.isArray(data) ? data : [];
|
return Array.isArray(data) ? data : [];
|
||||||
});
|
});
|
||||||
const cardContentStyle = computed(() => {
|
const cardContentStyle = computed(() => {
|
||||||
@@ -295,7 +301,7 @@ const {
|
|||||||
isDisabled: isRightButtonDisabled,
|
isDisabled: isRightButtonDisabled,
|
||||||
click: rightButtonClick,
|
click: rightButtonClick,
|
||||||
} = useContextMenu(() => props.rightButtons);
|
} = useContextMenu(() => props.rightButtons);
|
||||||
const getTableData = () => (Array.isArray(attrs.data) ? attrs.data : []);
|
const getTableData = () => props.data || [];
|
||||||
const isRowSelectable = (row: any) => {
|
const isRowSelectable = (row: any) => {
|
||||||
const selectable = (selectionColumn.value?.props as Record<string, any> | null)?.selectable;
|
const selectable = (selectionColumn.value?.props as Record<string, any> | null)?.selectable;
|
||||||
return typeof selectable === 'function' ? selectable(row) : true;
|
return typeof selectable === 'function' ? selectable(row) : true;
|
||||||
@@ -304,7 +310,6 @@ const {
|
|||||||
selectedRows,
|
selectedRows,
|
||||||
isRowSelected,
|
isRowSelected,
|
||||||
clearSelects,
|
clearSelects,
|
||||||
pruneSelection,
|
|
||||||
toggleSelection,
|
toggleSelection,
|
||||||
selectRow,
|
selectRow,
|
||||||
syncTableSelection,
|
syncTableSelection,
|
||||||
@@ -313,7 +318,18 @@ const {
|
|||||||
handleRowClick,
|
handleRowClick,
|
||||||
handleKeyDown: handleSelectionKeyDown,
|
handleKeyDown: handleSelectionKeyDown,
|
||||||
handleKeyUp,
|
handleKeyUp,
|
||||||
} = useTableSelection(tableRef, getTableData, (rows) => emit('update:selects', rows), isRowSelectable);
|
} = useTableSelection(
|
||||||
|
tableRef,
|
||||||
|
getTableData,
|
||||||
|
(rows) => emit('update:selects', rows),
|
||||||
|
isRowSelectable,
|
||||||
|
(row) => {
|
||||||
|
const key = attrs.rowKey ?? attrs['row-key'];
|
||||||
|
if (typeof key === 'function') return row == null ? undefined : key(row);
|
||||||
|
if (typeof key === 'string') return key.split('.').reduce((value, part) => value?.[part], row);
|
||||||
|
return row?.id;
|
||||||
|
},
|
||||||
|
);
|
||||||
const toggleCardSelection = (row: any, selected: boolean) => {
|
const toggleCardSelection = (row: any, selected: boolean) => {
|
||||||
selectRow(row, selected);
|
selectRow(row, selected);
|
||||||
};
|
};
|
||||||
@@ -329,10 +345,12 @@ const handleRightClick = (row, column, event) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function currentChange() {
|
function currentChange() {
|
||||||
|
clearSelects();
|
||||||
emit('search');
|
emit('search');
|
||||||
}
|
}
|
||||||
|
|
||||||
function sizeChange() {
|
function sizeChange() {
|
||||||
|
clearSelects();
|
||||||
props.paginationConfig.currentPage = 1;
|
props.paginationConfig.currentPage = 1;
|
||||||
localStorage.setItem(props.paginationConfig.cacheSizeKey, props.paginationConfig.pageSize);
|
localStorage.setItem(props.paginationConfig.cacheSizeKey, props.paginationConfig.pageSize);
|
||||||
emit('search');
|
emit('search');
|
||||||
@@ -446,7 +464,22 @@ watch([currentViewMode, tableData, () => props.syncCardContentHeight], scheduleC
|
|||||||
flush: 'post',
|
flush: 'post',
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(tableData, pruneSelection);
|
useTablePageState(
|
||||||
|
() => props.paginationConfig,
|
||||||
|
() => [
|
||||||
|
route.path,
|
||||||
|
currentNode.value,
|
||||||
|
typeof props.selectionContext === 'function' ? props.selectionContext() : props.selectionContext,
|
||||||
|
],
|
||||||
|
clearSelects,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Kept-alive tabs do not mount again. Reload their reset query on subsequent visits.
|
||||||
|
let hasActivated = false;
|
||||||
|
onActivated(() => {
|
||||||
|
if (hasActivated) emit('search');
|
||||||
|
hasActivated = true;
|
||||||
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('resize', calcHeight);
|
window.removeEventListener('resize', calcHeight);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { watch } from 'vue';
|
||||||
|
|
||||||
|
type Pagination = { currentPage?: number; pageSize?: number } | undefined;
|
||||||
|
|
||||||
|
/** Refreshing data leaves page state alone; only user navigation or a new query clears selection. */
|
||||||
|
export const useTablePageState = (
|
||||||
|
getPagination: () => Pagination,
|
||||||
|
getContext: () => unknown,
|
||||||
|
clearSelection: () => void,
|
||||||
|
) => {
|
||||||
|
watch(() => [getPagination()?.currentPage, getPagination()?.pageSize], clearSelection, { flush: 'sync' });
|
||||||
|
watch(
|
||||||
|
() => JSON.stringify(getContext()),
|
||||||
|
() => {
|
||||||
|
clearSelection();
|
||||||
|
const pagination = getPagination();
|
||||||
|
if (pagination) pagination.currentPage = 1;
|
||||||
|
},
|
||||||
|
{ flush: 'sync' },
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { shallowRef, ref, toRaw, type Ref } from 'vue';
|
import { shallowRef, ref, toRaw, watch, type Ref } from 'vue';
|
||||||
|
|
||||||
type TableRef = { refElTable?: any } | undefined;
|
type TableRef = { refElTable?: any } | undefined;
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ export const useTableSelection = (
|
|||||||
getTableData: () => any[],
|
getTableData: () => any[],
|
||||||
onSelectionChange: (rows: any[]) => void,
|
onSelectionChange: (rows: any[]) => void,
|
||||||
isRowSelectable: (row: any) => boolean = () => true,
|
isRowSelectable: (row: any) => boolean = () => true,
|
||||||
|
getRowKey: (row: any) => unknown = (row) => row?.id,
|
||||||
) => {
|
) => {
|
||||||
const selectedRows = shallowRef<any[]>([]);
|
const selectedRows = shallowRef<any[]>([]);
|
||||||
const shiftPressed = ref(false);
|
const shiftPressed = ref(false);
|
||||||
@@ -15,8 +16,13 @@ export const useTableSelection = (
|
|||||||
let isSyncingTableSelection = false;
|
let isSyncingTableSelection = false;
|
||||||
let skipNextSelectionChange = false;
|
let skipNextSelectionChange = false;
|
||||||
|
|
||||||
// Element Plus can return reactive proxies for rows supplied as plain objects.
|
let isRefreshingData = false;
|
||||||
const sameRow = (left: any, right: any) => toRaw(left) === toRaw(right);
|
const sameRow = (left: any, right: any) => {
|
||||||
|
if (left == null || right == null) return left === right;
|
||||||
|
const leftKey = getRowKey(left);
|
||||||
|
const rightKey = getRowKey(right);
|
||||||
|
return leftKey != null && rightKey != null ? leftKey === rightKey : toRaw(left) === toRaw(right);
|
||||||
|
};
|
||||||
const hasRow = (rows: any[], row: any) => rows.some((item) => sameRow(item, row));
|
const hasRow = (rows: any[], row: any) => rows.some((item) => sameRow(item, row));
|
||||||
const isRowSelected = (row: any) => hasRow(selectedRows.value, row);
|
const isRowSelected = (row: any) => hasRow(selectedRows.value, row);
|
||||||
const getTable = () => tableRef.value?.refElTable;
|
const getTable = () => tableRef.value?.refElTable;
|
||||||
@@ -39,8 +45,12 @@ export const useTableSelection = (
|
|||||||
const tableData = getTableData();
|
const tableData = getTableData();
|
||||||
const nextRows = selectedRows.value.filter((row) => hasRow(tableData, row) && isRowSelectable(row));
|
const nextRows = selectedRows.value.filter((row) => hasRow(tableData, row) && isRowSelectable(row));
|
||||||
const currentRows = table.getSelectionRows();
|
const currentRows = table.getSelectionRows();
|
||||||
currentRows.filter((row) => !hasRow(nextRows, row)).forEach((row) => table.toggleRowSelection(row, false));
|
currentRows
|
||||||
nextRows.filter((row) => !hasRow(currentRows, row)).forEach((row) => table.toggleRowSelection(row, true));
|
.filter((row) => !nextRows.some((item) => toRaw(item) === toRaw(row)))
|
||||||
|
.forEach((row) => table.toggleRowSelection(row, false));
|
||||||
|
nextRows
|
||||||
|
.filter((row) => !currentRows.some((item) => toRaw(item) === toRaw(row)))
|
||||||
|
.forEach((row) => table.toggleRowSelection(row, true));
|
||||||
} finally {
|
} finally {
|
||||||
isSyncingTableSelection = false;
|
isSyncingTableSelection = false;
|
||||||
}
|
}
|
||||||
@@ -73,7 +83,7 @@ export const useTableSelection = (
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
const handleSelectionChange = (rows: any[]) => {
|
const handleSelectionChange = (rows: any[]) => {
|
||||||
if (isSyncingTableSelection) {
|
if (isSyncingTableSelection || isRefreshingData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (skipNextSelectionChange) {
|
if (skipNextSelectionChange) {
|
||||||
@@ -104,16 +114,35 @@ export const useTableSelection = (
|
|||||||
rangeBaseRows.value = [];
|
rangeBaseRows.value = [];
|
||||||
};
|
};
|
||||||
const pruneSelection = () => {
|
const pruneSelection = () => {
|
||||||
const nextRows = selectedRows.value.filter((row) => hasRow(getTableData(), row));
|
const data = getTableData();
|
||||||
if (nextRows.length !== selectedRows.value.length) {
|
const nextRows = data.filter((row) => hasRow(selectedRows.value, row) && isRowSelectable(row));
|
||||||
|
if (nextRows.length !== selectedRows.value.length || nextRows.some((row, i) => row !== selectedRows.value[i])) {
|
||||||
setSelectedRows(nextRows);
|
setSelectedRows(nextRows);
|
||||||
}
|
}
|
||||||
if (lastSelectedRow.value && !hasRow(nextRows, lastSelectedRow.value)) {
|
lastSelectedRow.value = nextRows.find((row) => sameRow(row, lastSelectedRow.value)) ?? null;
|
||||||
lastSelectedRow.value = null;
|
rangeBaseRows.value = lastSelectedRow.value ? nextRows.filter((row) => hasRow(rangeBaseRows.value, row)) : [];
|
||||||
rangeBaseRows.value = [];
|
|
||||||
}
|
|
||||||
syncTableSelection();
|
|
||||||
};
|
};
|
||||||
|
// Guard before the child table reacts to replacement data and emits an empty selection.
|
||||||
|
watch(
|
||||||
|
getTableData,
|
||||||
|
() => {
|
||||||
|
isRefreshingData = true;
|
||||||
|
skipNextSelectionChange = false;
|
||||||
|
pruneSelection();
|
||||||
|
},
|
||||||
|
{ flush: 'sync' },
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
getTableData,
|
||||||
|
() => {
|
||||||
|
try {
|
||||||
|
syncTableSelection();
|
||||||
|
} finally {
|
||||||
|
isRefreshingData = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ flush: 'post' },
|
||||||
|
);
|
||||||
const toggleSelection = () => {
|
const toggleSelection = () => {
|
||||||
const selectableRows = getTableData().filter(isRowSelectable);
|
const selectableRows = getTableData().filter(isRowSelectable);
|
||||||
const allSelected = selectableRows.length > 0 && selectableRows.every(isRowSelected);
|
const allSelected = selectableRows.length > 0 && selectableRows.every(isRowSelected);
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { reactive, type UnwrapNestedRefs } from 'vue';
|
import { reactive, type UnwrapNestedRefs } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
|
||||||
import { getPageState } from '@/utils/page-state-cache';
|
|
||||||
|
|
||||||
|
/** A new page visit starts from its defaults instead of restoring the previous page number. */
|
||||||
export const usePageState = <T extends object>(factory: () => T): UnwrapNestedRefs<T> => {
|
export const usePageState = <T extends object>(factory: () => T): UnwrapNestedRefs<T> => {
|
||||||
const route = useRoute();
|
return reactive(factory());
|
||||||
const { currentNode } = useGlobalStore();
|
|
||||||
const key = `${currentNode.value}:${String(route.name || route.path)}`;
|
|
||||||
return reactive(getPageState(key, factory));
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import GlobalStore from '@/store/modules/global';
|
||||||
|
import router from '@/routers/router';
|
||||||
|
import { bindSearchReset } from '@/utils/search-reset';
|
||||||
|
|
||||||
|
/** Kept as a compatible entry point; searches now reset on tab, node or resource changes. */
|
||||||
|
export const useSearchPersistence = (
|
||||||
|
page: string,
|
||||||
|
fields: Parameters<typeof bindSearchReset>[1],
|
||||||
|
scope?: () => string | number | undefined,
|
||||||
|
) => {
|
||||||
|
const { currentNode } = storeToRefs(GlobalStore());
|
||||||
|
return bindSearchReset(() => [router.currentRoute.value.path, currentNode.value, page, scope?.()], fields);
|
||||||
|
};
|
||||||
@@ -739,6 +739,11 @@ const message = {
|
|||||||
keyspaceMisses: 'Number of failed attempts to find the database key.',
|
keyspaceMisses: 'Number of failed attempts to find the database key.',
|
||||||
hit: 'Find the database key hit ratio.',
|
hit: 'Find the database key hit ratio.',
|
||||||
latestForkUsec: 'The number of microseconds spent on the last fork() operation.',
|
latestForkUsec: 'The number of microseconds spent on the last fork() operation.',
|
||||||
|
redisCliEnableTask: 'Enable remote Redis terminal',
|
||||||
|
redisCliEnabling: 'Enabling remote Redis terminal',
|
||||||
|
redisCliEnabled: 'Remote Redis terminal enabled',
|
||||||
|
redisCliEnableFailed: 'Failed to enable remote Redis terminal. Check the task log and retry.',
|
||||||
|
redisCliTaskSubmitted: 'Task submitted. View progress in the task center.',
|
||||||
redisCliHelper: '"redis-cli" not detected. Enable the service first.',
|
redisCliHelper: '"redis-cli" not detected. Enable the service first.',
|
||||||
redisQuickCmd: 'Redis quick commands',
|
redisQuickCmd: 'Redis quick commands',
|
||||||
recoverHelper: 'This will overwrite data with [{0}]. Continue?',
|
recoverHelper: 'This will overwrite data with [{0}]. Continue?',
|
||||||
@@ -1841,6 +1846,11 @@ const message = {
|
|||||||
imageName: 'Image name',
|
imageName: 'Image name',
|
||||||
pull: 'Pull',
|
pull: 'Pull',
|
||||||
path: 'Path',
|
path: 'Path',
|
||||||
|
imageImportSource: 'Import source',
|
||||||
|
imageUploadDrop: 'Drop an image file here, or click to upload',
|
||||||
|
imageUploadHelper:
|
||||||
|
'Any file format is allowed. Docker validates the contents. Uploaded files are retained for retrying imports or manual deletion.',
|
||||||
|
imageImportSubmitted: 'Image import task submitted. Check the task log for the result.',
|
||||||
importImage: 'Import',
|
importImage: 'Import',
|
||||||
buildArgs: 'Build Arguments',
|
buildArgs: 'Build Arguments',
|
||||||
imageBuild: 'Build',
|
imageBuild: 'Build',
|
||||||
|
|||||||
@@ -709,6 +709,11 @@ const message = {
|
|||||||
keyspaceMisses: '尋找資料庫鍵失敗的次數',
|
keyspaceMisses: '尋找資料庫鍵失敗的次數',
|
||||||
hit: '尋找資料庫鍵命中率',
|
hit: '尋找資料庫鍵命中率',
|
||||||
latestForkUsec: '最近一次 fork() 操作耗費的微秒數',
|
latestForkUsec: '最近一次 fork() 操作耗費的微秒數',
|
||||||
|
redisCliEnableTask: '啟用 Redis 遠端終端',
|
||||||
|
redisCliEnabling: '正在啟用 Redis 遠端終端',
|
||||||
|
redisCliEnabled: 'Redis 遠端終端已啟用',
|
||||||
|
redisCliEnableFailed: 'Redis 遠端終端啟用失敗,請查看任務日誌後重試',
|
||||||
|
redisCliTaskSubmitted: '啟用任務已提交,可在任務中心查看進度',
|
||||||
redisCliHelper: '未偵測到 redis-cli 服務,請先啟用服務',
|
redisCliHelper: '未偵測到 redis-cli 服務,請先啟用服務',
|
||||||
redisQuickCmd: 'Redis 快速指令',
|
redisQuickCmd: 'Redis 快速指令',
|
||||||
recoverHelper: '即將使用 [{0}] 對資料進行覆蓋,是否繼續?',
|
recoverHelper: '即將使用 [{0}] 對資料進行覆蓋,是否繼續?',
|
||||||
@@ -1746,6 +1751,10 @@ const message = {
|
|||||||
delInsecureHelper: '刪除授信需要重新啟動 docker 服務,是否刪除?',
|
delInsecureHelper: '刪除授信需要重新啟動 docker 服務,是否刪除?',
|
||||||
pull: '拉取',
|
pull: '拉取',
|
||||||
path: '路徑',
|
path: '路徑',
|
||||||
|
imageImportSource: '匯入方式',
|
||||||
|
imageUploadDrop: '拖曳映像檔案至此處,或點擊上傳',
|
||||||
|
imageUploadHelper: '不限制檔案格式,由 Docker 驗證內容。上傳的檔案會保留,可重試匯入或手動刪除。',
|
||||||
|
imageImportSubmitted: '映像匯入任務已提交,請查看任務日誌',
|
||||||
importImage: '匯入映像',
|
importImage: '匯入映像',
|
||||||
imageBuild: '構建映像',
|
imageBuild: '構建映像',
|
||||||
buildArgs: '構建參數',
|
buildArgs: '構建參數',
|
||||||
|
|||||||
@@ -722,6 +722,11 @@ const message = {
|
|||||||
keyspaceMisses: '查找数据库键失败的次数',
|
keyspaceMisses: '查找数据库键失败的次数',
|
||||||
hit: '查找数据库键命中率',
|
hit: '查找数据库键命中率',
|
||||||
latestForkUsec: '最近一次 fork() 操作耗费的微秒数',
|
latestForkUsec: '最近一次 fork() 操作耗费的微秒数',
|
||||||
|
redisCliEnableTask: '启用 Redis 远程终端',
|
||||||
|
redisCliEnabling: '正在启用 Redis 远程终端',
|
||||||
|
redisCliEnabled: 'Redis 远程终端已启用',
|
||||||
|
redisCliEnableFailed: 'Redis 远程终端启用失败,请查看任务日志后重试',
|
||||||
|
redisCliTaskSubmitted: '启用任务已提交,可在任务中心查看进度',
|
||||||
redisCliHelper: '未检测到 redis-cli 服务,请先启用服务!',
|
redisCliHelper: '未检测到 redis-cli 服务,请先启用服务!',
|
||||||
redisQuickCmd: 'Redis 快速命令',
|
redisQuickCmd: 'Redis 快速命令',
|
||||||
|
|
||||||
@@ -1766,6 +1771,10 @@ const message = {
|
|||||||
delInsecureHelper: '删除授信需要重启 docker 服务,是否删除?',
|
delInsecureHelper: '删除授信需要重启 docker 服务,是否删除?',
|
||||||
pull: '拉取',
|
pull: '拉取',
|
||||||
path: '路径',
|
path: '路径',
|
||||||
|
imageImportSource: '导入方式',
|
||||||
|
imageUploadDrop: '拖拽镜像文件到此处,或点击上传',
|
||||||
|
imageUploadHelper: '不限制文件格式,由 Docker 校验内容。上传的文件会保留,可重试导入或手动删除。',
|
||||||
|
imageImportSubmitted: '镜像导入任务已提交,请查看任务日志',
|
||||||
importImage: '导入镜像',
|
importImage: '导入镜像',
|
||||||
imageBuild: '构建镜像',
|
imageBuild: '构建镜像',
|
||||||
buildArgs: '构建参数',
|
buildArgs: '构建参数',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ref, reactive } from 'vue';
|
import { ref, reactive, toRef } from 'vue';
|
||||||
import { MsgError } from '@/utils/message';
|
import { MsgError } from '@/utils/message';
|
||||||
import { checkStreamAuth } from '@/utils/stream-auth';
|
import { checkStreamAuth } from '@/utils/stream-auth';
|
||||||
|
|
||||||
@@ -46,6 +47,17 @@ export const ProcessStore = defineStore('ProcessStore', () => {
|
|||||||
port: undefined,
|
port: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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'),
|
||||||
|
});
|
||||||
|
|
||||||
let pendingRequestType: 'ps' | 'net' | null = null;
|
let pendingRequestType: 'ps' | 'net' | null = null;
|
||||||
|
|
||||||
let queuedRequestType: 'ps' | 'net' | null = null;
|
let queuedRequestType: 'ps' | 'net' | null = null;
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { watch, type Ref } from 'vue';
|
||||||
|
|
||||||
|
type SearchValue = string | number | undefined;
|
||||||
|
type SearchFields = Record<string, Ref<SearchValue>>;
|
||||||
|
|
||||||
|
export const bindSearchPersistence = (key: () => string | undefined, fields: SearchFields) => {
|
||||||
|
const defaults = Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value]));
|
||||||
|
let initialized = false;
|
||||||
|
let restoring = false;
|
||||||
|
let activeKey: string | undefined;
|
||||||
|
|
||||||
|
const stopRestore = watch(
|
||||||
|
key,
|
||||||
|
(value) => {
|
||||||
|
activeKey = value ? `1panel:search:v1:${value}` : undefined;
|
||||||
|
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 {
|
||||||
|
// Invalid or unavailable storage must not prevent a page from loading.
|
||||||
|
}
|
||||||
|
restoring = true;
|
||||||
|
for (const [name, field] of Object.entries(fields)) {
|
||||||
|
const value = saved[name];
|
||||||
|
const initial = defaults[name];
|
||||||
|
const fallback = initialized ? (typeof initial === 'string' ? '' : undefined) : initial;
|
||||||
|
const valid =
|
||||||
|
(typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value))) &&
|
||||||
|
(fallback === undefined || typeof value === typeof fallback);
|
||||||
|
field.value =
|
||||||
|
value === null
|
||||||
|
? typeof initial === 'string'
|
||||||
|
? ''
|
||||||
|
: undefined
|
||||||
|
: valid
|
||||||
|
? (value as SearchValue)
|
||||||
|
: fallback;
|
||||||
|
}
|
||||||
|
restoring = false;
|
||||||
|
initialized = true;
|
||||||
|
},
|
||||||
|
{ immediate: true, flush: 'sync' },
|
||||||
|
);
|
||||||
|
|
||||||
|
const stopSave = 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 {
|
||||||
|
// Search remains usable when browser storage is disabled or full.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ flush: 'sync' },
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
stopRestore();
|
||||||
|
stopSave();
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { watch, type Ref } from 'vue';
|
||||||
|
|
||||||
|
type SearchFields = Record<string, Ref<string | number | undefined>>;
|
||||||
|
|
||||||
|
/** Search terms belong to the current tab visit, not browser storage or cached pages. */
|
||||||
|
export const bindSearchReset = (context: () => unknown, fields: SearchFields) => {
|
||||||
|
const emptyValues = Object.fromEntries(
|
||||||
|
Object.entries(fields).map(([name, field]) => [name, typeof field.value === 'string' ? '' : undefined]),
|
||||||
|
);
|
||||||
|
return watch(
|
||||||
|
() => JSON.stringify(context()),
|
||||||
|
() => {
|
||||||
|
for (const [name, field] of Object.entries(fields)) field.value = emptyValues[name];
|
||||||
|
},
|
||||||
|
{ immediate: true, flush: 'sync' },
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -24,6 +24,7 @@ const formatSkillInstallTask = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const taskTextMap: Record<string, () => string> = {
|
const taskTextMap: Record<string, () => string> = {
|
||||||
|
RedisCliEnable: () => i18n.global.t('database.redisCliEnableTask'),
|
||||||
BatchInstallAgent: () => formatBatchAgentTask('Install'),
|
BatchInstallAgent: () => formatBatchAgentTask('Install'),
|
||||||
DispatchAgentInstallTasks: () => formatDispatchAgentTask('Install'),
|
DispatchAgentInstallTasks: () => formatDispatchAgentTask('Install'),
|
||||||
BatchUpgradeAgent: () => formatBatchAgentTask('Upgrade'),
|
BatchUpgradeAgent: () => formatBatchAgentTask('Upgrade'),
|
||||||
|
|||||||
@@ -172,6 +172,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
@@ -192,6 +193,11 @@ const searching = ref(false);
|
|||||||
const operating = ref('');
|
const operating = ref('');
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
const marketKeyword = ref('');
|
const marketKeyword = ref('');
|
||||||
|
useSearchPersistence(
|
||||||
|
'ai/agents/agent/config/tabs/plugins',
|
||||||
|
{ keyword, marketKeyword },
|
||||||
|
() => agentId.value || undefined,
|
||||||
|
);
|
||||||
const origin = ref('');
|
const origin = ref('');
|
||||||
const status = ref('');
|
const status = ref('');
|
||||||
const page = ref(1);
|
const page = ref(1);
|
||||||
@@ -299,8 +305,6 @@ async function operate(plugin: AI.AgentPluginItem, action: PluginOperate) {
|
|||||||
async function load(id: number) {
|
async function load(id: number) {
|
||||||
agentId.value = id;
|
agentId.value = id;
|
||||||
mode.value = 'installed';
|
mode.value = 'installed';
|
||||||
keyword.value = '';
|
|
||||||
marketKeyword.value = '';
|
|
||||||
origin.value = '';
|
origin.value = '';
|
||||||
status.value = '';
|
status.value = '';
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
|
|||||||
@@ -138,6 +138,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
@@ -168,6 +169,11 @@ const getDefaultMarketSource = (): HermesSkillMarketSource => {
|
|||||||
const marketSource = ref<HermesSkillMarketSource>(getDefaultMarketSource());
|
const marketSource = ref<HermesSkillMarketSource>(getDefaultMarketSource());
|
||||||
const marketSearched = ref(false);
|
const marketSearched = ref(false);
|
||||||
const agentId = ref(0);
|
const agentId = ref(0);
|
||||||
|
useSearchPersistence(
|
||||||
|
'ai/agents/agent/config/tabs/skills/hermes',
|
||||||
|
{ installedKeyword, marketKeyword },
|
||||||
|
() => agentId.value || undefined,
|
||||||
|
);
|
||||||
const installedSkills = ref<AI.AgentSkillItem[]>([]);
|
const installedSkills = ref<AI.AgentSkillItem[]>([]);
|
||||||
const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
|
const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
|
||||||
const installingSkill = ref('');
|
const installingSkill = ref('');
|
||||||
@@ -315,8 +321,6 @@ const handleModeChange = async (value: SkillViewMode) => {
|
|||||||
const load = async (id: number) => {
|
const load = async (id: number) => {
|
||||||
agentId.value = id;
|
agentId.value = id;
|
||||||
mode.value = 'market';
|
mode.value = 'market';
|
||||||
installedKeyword.value = '';
|
|
||||||
marketKeyword.value = '';
|
|
||||||
marketSource.value = getDefaultMarketSource();
|
marketSource.value = getDefaultMarketSource();
|
||||||
marketSearched.value = false;
|
marketSearched.value = false;
|
||||||
installedSkills.value = [];
|
installedSkills.value = [];
|
||||||
|
|||||||
@@ -142,6 +142,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { Refresh, Search } from '@element-plus/icons-vue';
|
import { Refresh, Search } from '@element-plus/icons-vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
@@ -180,6 +181,11 @@ const getDefaultMarketSource = (): SkillMarketSource => {
|
|||||||
const marketSource = ref<SkillMarketSource>(getDefaultMarketSource());
|
const marketSource = ref<SkillMarketSource>(getDefaultMarketSource());
|
||||||
const marketSearched = ref(false);
|
const marketSearched = ref(false);
|
||||||
const agentId = ref(0);
|
const agentId = ref(0);
|
||||||
|
useSearchPersistence(
|
||||||
|
'ai/agents/agent/config/tabs/skills/openclaw',
|
||||||
|
{ installedKeyword, marketKeyword },
|
||||||
|
() => agentId.value || undefined,
|
||||||
|
);
|
||||||
const skills = ref<AI.AgentSkillItem[]>([]);
|
const skills = ref<AI.AgentSkillItem[]>([]);
|
||||||
const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
|
const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
|
||||||
const updatingSkill = ref('');
|
const updatingSkill = ref('');
|
||||||
|
|||||||
@@ -272,6 +272,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { deleteAgentCheck, pageAgents, resetAgentToken, unbindAgentWebsite, updateAgentRemark } from '@/api/modules/ai';
|
import { deleteAgentCheck, pageAgents, resetAgentToken, unbindAgentWebsite, updateAgentRemark } from '@/api/modules/ai';
|
||||||
@@ -340,6 +341,7 @@ const isActive = ref(false);
|
|||||||
const isExist = ref(false);
|
const isExist = ref(false);
|
||||||
const noApp = ref(false);
|
const noApp = ref(false);
|
||||||
const searchName = ref('');
|
const searchName = ref('');
|
||||||
|
useSearchPersistence('ai/agents/agent/index', { search: searchName });
|
||||||
const defaultHttpsPort = ref(443);
|
const defaultHttpsPort = ref(443);
|
||||||
const openrestyPortLoaded = ref(false);
|
const openrestyPortLoaded = ref(false);
|
||||||
const websiteDomainsMap = ref<Record<number, Website.Domain[]>>({});
|
const websiteDomainsMap = ref<Record<number, Website.Domain[]>>({});
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import { deleteAgentAccount, getAgentProviders, pageAgentAccounts } from '@/api/modules/ai';
|
import { deleteAgentAccount, getAgentProviders, pageAgentAccounts } from '@/api/modules/ai';
|
||||||
import { AI } from '@/api/interface/ai';
|
import { AI } from '@/api/interface/ai';
|
||||||
@@ -85,6 +86,7 @@ const items = ref<AI.AgentAccountItem[]>([]);
|
|||||||
const addRef = ref();
|
const addRef = ref();
|
||||||
const modelPoolRef = ref();
|
const modelPoolRef = ref();
|
||||||
const searchName = ref('');
|
const searchName = ref('');
|
||||||
|
useSearchPersistence('ai/agents/model/index', { search: searchName });
|
||||||
const apiType = ref('');
|
const apiType = ref('');
|
||||||
const apiTypeOptions = ref<string[]>([]);
|
const apiTypeOptions = ref<string[]>([]);
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [searchName]"
|
||||||
|
row-key="id"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
v-model:selects="selects"
|
v-model:selects="selects"
|
||||||
:class="{ mask: maskShow }"
|
:class="{ mask: maskShow }"
|
||||||
@@ -184,6 +186,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import AppStatus from '@/components/app-status/index.vue';
|
import AppStatus from '@/components/app-status/index.vue';
|
||||||
import AddDialog from '@/views/ai/model/ollama/add/index.vue';
|
import AddDialog from '@/views/ai/model/ollama/add/index.vue';
|
||||||
import Conn from '@/views/ai/model/ollama/conn/index.vue';
|
import Conn from '@/views/ai/model/ollama/conn/index.vue';
|
||||||
@@ -234,6 +237,7 @@ const paginationConfig = reactive({
|
|||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('ai/model/ollama/index', { search: searchName });
|
||||||
const appInstallID = ref(0);
|
const appInstallID = ref(0);
|
||||||
|
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
|
|||||||
@@ -70,8 +70,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { App } from '@/api/interface/app';
|
import { App } from '@/api/interface/app';
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { toRef, onMounted, reactive, ref } from 'vue';
|
||||||
import { searchApp, syncApp, syncCutomAppStore, syncLocalApp, getCurrentNodeCustomAppConfig } from '@/api/modules/app';
|
import { searchApp, syncApp, syncCutomAppStore, syncLocalApp, getCurrentNodeCustomAppConfig } from '@/api/modules/app';
|
||||||
import Install from '../detail/install/index.vue';
|
import Install from '../detail/install/index.vue';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
@@ -104,6 +105,7 @@ const req = reactive({
|
|||||||
resource: 'all',
|
resource: 'all',
|
||||||
showCurrentArch: false,
|
showCurrentArch: false,
|
||||||
});
|
});
|
||||||
|
useSearchPersistence('app-store/apps/index', { search: toRef(req, 'name') });
|
||||||
|
|
||||||
const apps = ref<App.AppItem[]>([]);
|
const apps = ref<App.AppItem[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|||||||
@@ -139,6 +139,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import AppCard from '@/views/app-store/installed/app/card.vue';
|
import AppCard from '@/views/app-store/installed/app/card.vue';
|
||||||
import Backups from '@/components/backup/index.vue';
|
import Backups from '@/components/backup/index.vue';
|
||||||
import Uploads from '@/components/upload/index.vue';
|
import Uploads from '@/components/upload/index.vue';
|
||||||
@@ -156,7 +157,7 @@ import IgnoreApp from '@/views/app-store/installed/ignore/create/index.vue';
|
|||||||
import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
||||||
|
|
||||||
import { searchAppInstalled, installedOp, appInstalledDeleteCheck, updateAppInstallSort } from '@/api/modules/app';
|
import { searchAppInstalled, installedOp, appInstalledDeleteCheck, updateAppInstallSort } from '@/api/modules/app';
|
||||||
import { onMounted, onUnmounted, reactive, ref, nextTick } from 'vue';
|
import { toRef, onMounted, onUnmounted, reactive, ref, nextTick } from 'vue';
|
||||||
import Sortable from 'sortablejs';
|
import Sortable from 'sortablejs';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
@@ -210,6 +211,7 @@ const searchReq = reactive({
|
|||||||
update: false,
|
update: false,
|
||||||
sync: false,
|
sync: false,
|
||||||
});
|
});
|
||||||
|
useSearchPersistence('app-store/installed/index', { search: toRef(searchReq, 'name') });
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const activeName = ref(i18n.global.t('app.installed'));
|
const activeName = ref(i18n.global.t('app.installed'));
|
||||||
const mode = ref('installed');
|
const mode = ref('installed');
|
||||||
|
|||||||
@@ -521,6 +521,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { computed, h, ref } from 'vue';
|
import { computed, h, ref } from 'vue';
|
||||||
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
|
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
|
||||||
import ContainerLog from '@/components/log/container/index.vue';
|
import ContainerLog from '@/components/log/container/index.vue';
|
||||||
@@ -575,6 +576,7 @@ const dialogBackupRef = ref();
|
|||||||
const uploadRef = ref();
|
const uploadRef = ref();
|
||||||
|
|
||||||
const searchName = ref('');
|
const searchName = ref('');
|
||||||
|
useSearchPersistence('container/compose/index', { search: searchName });
|
||||||
const includeAppStore = ref(localStorage.getItem('includeAppStore') !== 'false');
|
const includeAppStore = ref(localStorage.getItem('includeAppStore') !== 'false');
|
||||||
const showType = ref('compose');
|
const showType = ref('compose');
|
||||||
const containerStats = ref<any[]>([]);
|
const containerStats = ref<any[]>([]);
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [searchName, activeTag, includeAppStore, props.filters]"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
v-model:view-mode="viewMode"
|
v-model:view-mode="viewMode"
|
||||||
v-model:selects="selects"
|
v-model:selects="selects"
|
||||||
@@ -473,6 +474,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import PruneDialog from '@/views/container/container/prune/index.vue';
|
import PruneDialog from '@/views/container/container/prune/index.vue';
|
||||||
import RenameDialog from '@/views/container/container/rename/index.vue';
|
import RenameDialog from '@/views/container/container/rename/index.vue';
|
||||||
import UpgradeDialog from '@/views/container/container/upgrade/index.vue';
|
import UpgradeDialog from '@/views/container/container/upgrade/index.vue';
|
||||||
@@ -488,7 +490,7 @@ import Uploads from '@/components/upload/index.vue';
|
|||||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||||
import ContainerLogDialog from '@/components/log/container-drawer/index.vue';
|
import ContainerLogDialog from '@/components/log/container-drawer/index.vue';
|
||||||
import Status from '@/components/status/index.vue';
|
import Status from '@/components/status/index.vue';
|
||||||
import { computed, reactive, ref } from 'vue';
|
import { computed, onBeforeUnmount, reactive, ref } from 'vue';
|
||||||
import {
|
import {
|
||||||
containerItemStats,
|
containerItemStats,
|
||||||
containerListStats,
|
containerListStats,
|
||||||
@@ -534,6 +536,7 @@ const paginationConfig = reactive({
|
|||||||
order: 'ascending',
|
order: 'ascending',
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('container/container/index', { search: searchName });
|
||||||
const dialogUpgradeRef = ref();
|
const dialogUpgradeRef = ref();
|
||||||
const dialogCommitRef = ref();
|
const dialogCommitRef = ref();
|
||||||
const dialogPortJumpRef = ref();
|
const dialogPortJumpRef = ref();
|
||||||
@@ -700,47 +703,87 @@ const dialogContainerLogRef = ref();
|
|||||||
const dialogRenameRef = ref();
|
const dialogRenameRef = ref();
|
||||||
const dialogPruneRef = ref();
|
const dialogPruneRef = ref();
|
||||||
|
|
||||||
const search = async (column?: any) => {
|
let requestController: AbortController | undefined;
|
||||||
if (!isActive.value || !isExist.value) {
|
let requestVersion = 0;
|
||||||
|
let refreshPending = false;
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
requestVersion++;
|
||||||
|
requestController?.abort();
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadContainers = async (background = false, column?: any) => {
|
||||||
|
if (!isActive.value || !isExist.value || (background && refreshPending)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
requestController?.abort();
|
||||||
|
const controller = new AbortController();
|
||||||
|
requestController = controller;
|
||||||
|
const version = ++requestVersion;
|
||||||
|
const isCurrent = () => version === requestVersion && !controller.signal.aborted;
|
||||||
|
refreshPending = true;
|
||||||
|
loading.value = !background;
|
||||||
localStorage.setItem('includeAppStore', includeAppStore.value ? 'true' : 'false');
|
localStorage.setItem('includeAppStore', includeAppStore.value ? 'true' : 'false');
|
||||||
let filterItem = (router.currentRoute.value.query?.filters as string) || '';
|
|
||||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||||
paginationConfig.order = column?.order ? column.order : paginationConfig.order;
|
paginationConfig.order = column?.order ? column.order : paginationConfig.order;
|
||||||
let params = {
|
const params = {
|
||||||
name: searchName.value,
|
name: searchName.value,
|
||||||
state: paginationConfig.state || 'all',
|
state: paginationConfig.state || 'all',
|
||||||
page: paginationConfig.currentPage,
|
page: paginationConfig.currentPage,
|
||||||
pageSize: paginationConfig.pageSize,
|
pageSize: paginationConfig.pageSize,
|
||||||
filters: filterItem,
|
filters: props.filters || (router.currentRoute.value.query?.filters as string) || '',
|
||||||
orderBy: paginationConfig.orderBy,
|
orderBy: paginationConfig.orderBy,
|
||||||
order: paginationConfig.order,
|
order: paginationConfig.order,
|
||||||
excludeAppStore: !includeAppStore.value,
|
excludeAppStore: !includeAppStore.value,
|
||||||
};
|
};
|
||||||
loading.value = true;
|
// Counts and resource samples must never hold up the table itself.
|
||||||
const [containerResult, statsResult, statusResult] = await Promise.allSettled([
|
const statusRequest = loadContainerStatus(undefined, true, controller.signal)
|
||||||
searchContainer(params),
|
.then((res) => {
|
||||||
containerListStats(),
|
if (isCurrent()) {
|
||||||
loadContainerStatus(),
|
updateTags(res.data || {});
|
||||||
]);
|
}
|
||||||
loading.value = false;
|
})
|
||||||
|
.catch(() => {});
|
||||||
if (containerResult.status === 'fulfilled') {
|
try {
|
||||||
const containers = containerResult.value.data.items || [];
|
const res = await searchContainer(params, undefined, controller.signal);
|
||||||
|
if (!isCurrent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const containers = res.data.items || [];
|
||||||
syncContainerRows(containers);
|
syncContainerRows(containers);
|
||||||
paginationConfig.total = containerResult.value.data.total;
|
paginationConfig.total = res.data.total;
|
||||||
|
loading.value = false;
|
||||||
|
// Clear old usage when a previously running container has stopped.
|
||||||
|
applyStatsToRows(
|
||||||
|
containers
|
||||||
|
.filter((item) => item.state !== 'running')
|
||||||
|
.map((item) => ({
|
||||||
|
containerID: item.containerID,
|
||||||
|
...Object.fromEntries(statFields.map((field) => [field, 0])),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
const ids = containers.filter((item) => item.state === 'running').map((item) => item.containerID);
|
||||||
|
if (ids.length > 0) {
|
||||||
|
const stats = await containerListStats(undefined, ids, controller.signal);
|
||||||
|
if (isCurrent()) {
|
||||||
|
applyStatsToRows(stats.data || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statsResult.status === 'fulfilled') {
|
|
||||||
applyStatsToRows(statsResult.value.data || []);
|
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
if (statusResult.status === 'fulfilled') {
|
// The HTTP layer reports failures; keep the last successful rows visible.
|
||||||
updateTags(statusResult.value.data || {});
|
} finally {
|
||||||
|
if (isCurrent()) {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
await statusRequest;
|
||||||
|
if (isCurrent()) {
|
||||||
|
refreshPending = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const search = (column?: any) => loadContainers(false, column);
|
||||||
|
|
||||||
const searchWithStatus = (item: string) => {
|
const searchWithStatus = (item: string) => {
|
||||||
activeTag.value = item;
|
activeTag.value = item;
|
||||||
paginationConfig.state = activeTag.value;
|
paginationConfig.state = activeTag.value;
|
||||||
@@ -769,21 +812,7 @@ const changePinned = (row: any, isPinned: boolean) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = () => loadContainers(true);
|
||||||
let filterItem = props.filters ? props.filters : '';
|
|
||||||
let params = {
|
|
||||||
name: searchName.value,
|
|
||||||
state: paginationConfig.state || 'all',
|
|
||||||
page: paginationConfig.currentPage,
|
|
||||||
pageSize: paginationConfig.pageSize,
|
|
||||||
filters: filterItem,
|
|
||||||
orderBy: paginationConfig.orderBy,
|
|
||||||
order: paginationConfig.order,
|
|
||||||
};
|
|
||||||
const [containerResult, statsResult] = await Promise.all([searchContainer(params), containerListStats()]);
|
|
||||||
syncContainerRows(containerResult.data.items || []);
|
|
||||||
applyStatsToRows(statsResult.data || []);
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadSize = async (row: any) => {
|
const loadSize = async (row: any) => {
|
||||||
containerItemStats(row.containerID).then((res) => {
|
containerItemStats(row.containerID).then((res) => {
|
||||||
|
|||||||
@@ -157,7 +157,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref, computed } from 'vue';
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
|
import { toRef, reactive, ref, computed } from 'vue';
|
||||||
import { dateFormat } from '@/utils/date';
|
import { dateFormat } from '@/utils/date';
|
||||||
import { newUUID } from '@/utils/id';
|
import { newUUID } from '@/utils/id';
|
||||||
import { computeSize2 } from '@/utils/size';
|
import { computeSize2 } from '@/utils/size';
|
||||||
@@ -199,6 +200,7 @@ const paginationConfig = reactive({
|
|||||||
orderBy: 'createdAt',
|
orderBy: 'createdAt',
|
||||||
order: 'null',
|
order: 'null',
|
||||||
});
|
});
|
||||||
|
useSearchPersistence('container/image/index', { search: toRef(paginationConfig, 'name') });
|
||||||
const columns = ref([]);
|
const columns = ref([]);
|
||||||
|
|
||||||
const isActive = ref(false);
|
const isActive = ref(false);
|
||||||
|
|||||||
@@ -1,105 +1,294 @@
|
|||||||
<template>
|
<template>
|
||||||
<DrawerPro v-model="loadVisible" :header="$t('container.importImage')" @close="handleClose" size="small">
|
<DrawerPro
|
||||||
<el-form @submit.prevent v-loading="loading" ref="formRef" :model="form" label-position="top">
|
v-model="loadVisible"
|
||||||
<el-form-item :label="$t('container.path')" :rules="Rules.requiredInput" prop="path">
|
:header="$t('container.importImage')"
|
||||||
<el-input v-model="form.path" @input="handlePathChange">
|
:auto-close="!busy"
|
||||||
<template #prepend>
|
:confirm-before-close="true"
|
||||||
<el-button icon="Folder" @click="fileRef.acceptParams({ dir: false, multiple: true })" />
|
@before-close="beforeClose"
|
||||||
</template>
|
size="small"
|
||||||
</el-input>
|
>
|
||||||
|
<el-form @submit.prevent label-position="top">
|
||||||
|
<div class="flex items-center gap-3 mb-5">
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
:limit="1"
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
:disabled="busy || !!pendingTaskID"
|
||||||
|
:on-change="onFileChange"
|
||||||
|
:on-exceed="onFileExceed"
|
||||||
|
>
|
||||||
|
<el-button :disabled="busy || !!pendingTaskID" @click="source = 'local'">
|
||||||
|
{{ $t('database.localUpload') }}
|
||||||
|
</el-button>
|
||||||
|
</el-upload>
|
||||||
|
<el-button :disabled="busy" @click="onServerSelect">
|
||||||
|
{{ $t('database.hostSelect') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-form-item v-if="source === 'server'" :label="$t('container.path')">
|
||||||
|
<el-input v-model="form.path" :disabled="busy" @input="handlePathChange" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<template v-else>
|
||||||
|
<el-upload
|
||||||
|
v-if="!uploadedPath"
|
||||||
|
ref="dragUploadRef"
|
||||||
|
drag
|
||||||
|
:limit="1"
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
:disabled="busy || !!pendingTaskID"
|
||||||
|
:on-change="onFileChange"
|
||||||
|
:on-exceed="onFileExceed"
|
||||||
|
>
|
||||||
|
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
|
||||||
|
<div class="el-upload__text">{{ $t('container.imageUploadDrop') }}</div>
|
||||||
|
</el-upload>
|
||||||
|
<p class="input-help">{{ $t('container.imageUploadHelper') }}</p>
|
||||||
|
<template v-if="localFile">
|
||||||
|
<p class="break-all">{{ localFile.name }} ({{ computeSize(localFile.size) }})</p>
|
||||||
|
<el-progress
|
||||||
|
:percentage="uploadPercent"
|
||||||
|
:status="uploadFailed ? 'exception' : uploadedPath ? 'success' : undefined"
|
||||||
|
>
|
||||||
|
<template v-if="uploadFailed" #default>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
icon="CircleClose"
|
||||||
|
:title="$t('commons.button.delete')"
|
||||||
|
:aria-label="$t('commons.button.delete')"
|
||||||
|
:disabled="busy || !!pendingTaskID"
|
||||||
|
@click="removeUpload"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-progress>
|
||||||
|
<p v-if="uploadedPath" class="input-help break-all">{{ uploadedPath }}</p>
|
||||||
|
<el-button v-if="uploadFailed" :disabled="busy" @click="uploadLocalFile">
|
||||||
|
{{ $t('commons.button.retry') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="uploadedPath" :disabled="busy || !!pendingTaskID" @click="removeUpload">
|
||||||
|
{{ $t('commons.button.delete') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<el-button :disabled="busy" @click="loadVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
<el-button :disabled="loading" @click="loadVisible = false">
|
<el-button :disabled="!canImport" :loading="loading" type="primary" @click="onSubmit">
|
||||||
{{ $t('commons.button.cancel') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
:disabled="loading || !form.path.trim() || form.paths.length === 0"
|
|
||||||
type="primary"
|
|
||||||
@click="onSubmit(formRef)"
|
|
||||||
>
|
|
||||||
{{ $t('commons.button.import') }}
|
{{ $t('commons.button.import') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
</DrawerPro>
|
</DrawerPro>
|
||||||
<FileList ref="fileRef" @choose="loadLoadDir" />
|
<FileList v-if="loadVisible" ref="fileRef" @choose="loadLoadDir" />
|
||||||
<TaskLog ref="taskLogRef" width="70%" />
|
<TaskLog ref="taskLogRef" width="70%" @close="emit('search')" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import FileList from '@/components/file-list/index.vue';
|
import FileList from '@/components/file-list/index.vue';
|
||||||
import { reactive, ref } from 'vue';
|
import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue';
|
||||||
import { Rules } from '@/global/form-rules';
|
import { genFileId, type UploadFile, type UploadInstance, type UploadProps, type UploadRawFile } from 'element-plus';
|
||||||
import TaskLog from '@/components/log/task/index.vue';
|
import TaskLog from '@/components/log/task/index.vue';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { ElForm } from 'element-plus';
|
|
||||||
import { imageLoad } from '@/api/modules/container';
|
import { imageLoad } from '@/api/modules/container';
|
||||||
|
import { chunkUploadFileData, deleteFileByNode, stopChunkUpload } from '@/api/modules/files';
|
||||||
|
import { searchTasks } from '@/api/modules/log';
|
||||||
|
import { loadBaseDir } from '@/api/modules/setting';
|
||||||
|
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { computeSize } from '@/utils/size';
|
||||||
import { newUUID } from '@/utils/id';
|
import { newUUID } from '@/utils/id';
|
||||||
|
|
||||||
|
const { currentNode } = useGlobalStore();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const uploading = ref(false);
|
||||||
|
const busy = computed(() => loading.value || uploading.value);
|
||||||
const fileRef = ref();
|
const fileRef = ref();
|
||||||
const taskLogRef = ref();
|
const taskLogRef = ref();
|
||||||
|
const uploadRef = ref<UploadInstance>();
|
||||||
|
const dragUploadRef = ref<UploadInstance>();
|
||||||
const loadVisible = ref(false);
|
const loadVisible = ref(false);
|
||||||
const form = reactive({
|
const source = ref<'server' | 'local'>('local');
|
||||||
path: '',
|
const operateNode = ref('');
|
||||||
paths: [] as string[],
|
const form = reactive({ path: '', paths: [] as string[] });
|
||||||
taskID: '',
|
const localFile = ref<UploadRawFile>();
|
||||||
});
|
const uploadPercent = ref(0);
|
||||||
|
const uploadFailed = ref(false);
|
||||||
|
const uploadedPath = ref('');
|
||||||
|
const uploadDir = ref('');
|
||||||
|
const pendingTaskID = ref('');
|
||||||
|
let uploadID = '';
|
||||||
|
let uploadController: AbortController | undefined;
|
||||||
|
let taskTimer: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
let disposed = false;
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
const canImport = computed(
|
||||||
|
() =>
|
||||||
|
!busy.value && !pendingTaskID.value && (source.value === 'local' ? !!uploadedPath.value : !!form.paths.length),
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetLocalFile = () => {
|
||||||
|
localFile.value = undefined;
|
||||||
|
uploadedPath.value = '';
|
||||||
|
uploadDir.value = '';
|
||||||
|
uploadPercent.value = 0;
|
||||||
|
uploadFailed.value = false;
|
||||||
|
uploadRef.value?.clearFiles();
|
||||||
|
dragUploadRef.value?.clearFiles();
|
||||||
|
};
|
||||||
const acceptParams = () => {
|
const acceptParams = () => {
|
||||||
loadVisible.value = true;
|
if (busy.value) return;
|
||||||
|
if (operateNode.value !== currentNode.value) resetLocalFile();
|
||||||
|
operateNode.value = currentNode.value;
|
||||||
|
source.value = 'local';
|
||||||
form.path = '';
|
form.path = '';
|
||||||
form.paths = [];
|
form.paths = [];
|
||||||
|
loadVisible.value = true;
|
||||||
};
|
};
|
||||||
const handleClose = () => {
|
const beforeClose = (done: () => void) => {
|
||||||
loadVisible.value = false;
|
if (!busy.value) done();
|
||||||
|
};
|
||||||
|
const onServerSelect = () => {
|
||||||
|
if (busy.value) return;
|
||||||
|
source.value = 'server';
|
||||||
|
fileRef.value.acceptParams({ dir: false, multiple: true });
|
||||||
|
};
|
||||||
|
const onFileChange = (file: UploadFile) => {
|
||||||
|
if (!file.raw || busy.value || pendingTaskID.value) return;
|
||||||
|
source.value = 'local';
|
||||||
|
uploadedPath.value = '';
|
||||||
|
uploadDir.value = '';
|
||||||
|
localFile.value = file.raw;
|
||||||
|
void uploadLocalFile();
|
||||||
|
};
|
||||||
|
const onFileExceed: UploadProps['onExceed'] = (files) => {
|
||||||
|
if (busy.value || pendingTaskID.value || !files.length) return;
|
||||||
|
uploadRef.value?.clearFiles();
|
||||||
|
dragUploadRef.value?.clearFiles();
|
||||||
|
const file = files[0] as UploadRawFile;
|
||||||
|
file.uid = genFileId();
|
||||||
|
uploadRef.value?.handleStart(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
const uploadLocalFile = async () => {
|
||||||
|
const file = localFile.value;
|
||||||
type FormInstance = InstanceType<typeof ElForm>;
|
if (!file || busy.value) return;
|
||||||
const formRef = ref<FormInstance>();
|
uploading.value = true;
|
||||||
|
uploadFailed.value = false;
|
||||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
uploadPercent.value = 0;
|
||||||
if (!formEl || !form.path.trim() || form.paths.length === 0) return;
|
const node = operateNode.value;
|
||||||
formEl.validate(async (valid) => {
|
const id = newUUID();
|
||||||
if (!valid) return;
|
uploadID = id;
|
||||||
loading.value = true;
|
const controller = new AbortController();
|
||||||
form.taskID = newUUID();
|
uploadController = controller;
|
||||||
await imageLoad({ paths: form.paths, taskID: form.taskID })
|
try {
|
||||||
.then(() => {
|
const { data: baseDir } = await loadBaseDir(node);
|
||||||
loading.value = false;
|
if (controller.signal.aborted) return;
|
||||||
loadVisible.value = false;
|
const directory = `${baseDir}/uploads/image/${id}`;
|
||||||
openTaskLog(form.taskID);
|
const chunkSize = 1024 * 1024;
|
||||||
emit('search');
|
// Empty files also reach Docker, which validates the archive contents.
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
const count = Math.max(1, Math.ceil(file.size / chunkSize));
|
||||||
})
|
for (let index = 0; index < count; index++) {
|
||||||
.catch(() => {
|
const data = new FormData();
|
||||||
loading.value = false;
|
data.append('filename', file.name);
|
||||||
|
data.append('path', directory);
|
||||||
|
data.append('uploadID', id);
|
||||||
|
data.append('chunk', file.slice(index * chunkSize, (index + 1) * chunkSize));
|
||||||
|
data.append('chunkIndex', String(index));
|
||||||
|
data.append('chunkCount', String(count));
|
||||||
|
await chunkUploadFileData(data, {
|
||||||
|
headers: { CurrentNode: node },
|
||||||
|
signal: controller.signal,
|
||||||
|
onUploadProgress: (event) => {
|
||||||
|
const fraction = event.total ? Math.min(1, event.loaded / event.total) : 0;
|
||||||
|
uploadPercent.value = Math.min(99, Math.floor(((index + fraction) * 100) / count));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
};
|
uploadDir.value = directory;
|
||||||
|
uploadedPath.value = `${directory}/${file.name}`;
|
||||||
const openTaskLog = (taskID: string) => {
|
uploadPercent.value = 100;
|
||||||
taskLogRef.value.openWithTaskID(taskID);
|
MsgSuccess(i18n.global.t('file.uploadSuccess'));
|
||||||
};
|
} catch {
|
||||||
|
if (!controller.signal.aborted) uploadFailed.value = true;
|
||||||
const handlePathChange = () => {
|
await stopChunkUpload(id, node).catch(() => {});
|
||||||
if (!form.path.trim()) {
|
} finally {
|
||||||
form.paths = [];
|
uploading.value = false;
|
||||||
|
uploadController = undefined;
|
||||||
|
uploadID = '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadLoadDir = async (paths: string | string[]) => {
|
const removeUpload = async () => {
|
||||||
const newPaths = Array.isArray(paths) ? paths : [paths];
|
if (busy.value || pendingTaskID.value || !localFile.value) return;
|
||||||
form.paths = [...new Set(newPaths)];
|
loading.value = true;
|
||||||
form.path = form.paths.join('; ');
|
try {
|
||||||
|
if (uploadDir.value) {
|
||||||
|
await deleteFileByNode({ path: uploadDir.value, isDir: true, forceDelete: true }, operateNode.value);
|
||||||
|
}
|
||||||
|
resetLocalFile();
|
||||||
|
source.value = 'local';
|
||||||
|
form.path = '';
|
||||||
|
form.paths = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({
|
const pollTask = async (taskID: string, node: string) => {
|
||||||
acceptParams,
|
try {
|
||||||
|
const { data } = await searchTasks({ page: 1, pageSize: 1, type: '', status: '', taskID }, node);
|
||||||
|
const status = data.items?.[0]?.status;
|
||||||
|
if (status === 'Success' || status === 'Failed' || status === 'Canceled') {
|
||||||
|
pendingTaskID.value = '';
|
||||||
|
if (currentNode.value === node) emit('search');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Keep the upload protected while the task status is unknown.
|
||||||
|
}
|
||||||
|
if (!disposed) taskTimer = setTimeout(() => void pollTask(taskID, node), 3000);
|
||||||
|
};
|
||||||
|
const onSubmit = async () => {
|
||||||
|
if (!canImport.value) return;
|
||||||
|
loading.value = true;
|
||||||
|
const taskID = newUUID();
|
||||||
|
const node = operateNode.value;
|
||||||
|
try {
|
||||||
|
await imageLoad({ paths: source.value === 'local' ? [uploadedPath.value] : form.paths, taskID }, node);
|
||||||
|
pendingTaskID.value = taskID;
|
||||||
|
loadVisible.value = false;
|
||||||
|
taskLogRef.value.openWithTaskID(taskID, true, node);
|
||||||
|
MsgSuccess(i18n.global.t('container.imageImportSubmitted'));
|
||||||
|
void pollTask(taskID, node);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handlePathChange = () => {
|
||||||
|
form.paths = [
|
||||||
|
...new Set(
|
||||||
|
form.path
|
||||||
|
.split(';')
|
||||||
|
.map((path) => path.trim())
|
||||||
|
.filter(Boolean),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
const loadLoadDir = (paths: string | string[]) => {
|
||||||
|
if (currentNode.value !== operateNode.value) return;
|
||||||
|
source.value = 'server';
|
||||||
|
form.paths = [...new Set(Array.isArray(paths) ? paths : [paths])];
|
||||||
|
form.path = form.paths.join('; ');
|
||||||
|
};
|
||||||
|
watch(currentNode, () => {
|
||||||
|
loadVisible.value = false;
|
||||||
});
|
});
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
disposed = true;
|
||||||
|
clearTimeout(taskTimer);
|
||||||
|
uploadController?.abort();
|
||||||
|
if (uploadID) void stopChunkUpload(uploadID, operateNode.value).catch(() => {});
|
||||||
|
});
|
||||||
|
defineExpose({ acceptParams });
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [searchName]"
|
||||||
|
row-key="id"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
v-model:selects="selects"
|
v-model:selects="selects"
|
||||||
:data="data"
|
:data="data"
|
||||||
@@ -93,12 +95,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import CreateDialog from '@/views/container/network/create/index.vue';
|
import CreateDialog from '@/views/container/network/create/index.vue';
|
||||||
import DetailDrawer from '@/views/container/network/detail/index.vue';
|
import DetailDrawer from '@/views/container/network/detail/index.vue';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { dateFormat } from '@/utils/date';
|
import { dateFormat } from '@/utils/date';
|
||||||
import { newUUID } from '@/utils/id';
|
import { deleteNetwork, searchNetwork, inspect, cleanNetworks } from '@/api/modules/container';
|
||||||
import { deleteNetwork, searchNetwork, inspect, containerPrune } from '@/api/modules/container';
|
|
||||||
import { Container } from '@/api/interface/container';
|
import { Container } from '@/api/interface/container';
|
||||||
import TaskLog from '@/components/log/task/index.vue';
|
import TaskLog from '@/components/log/task/index.vue';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
@@ -118,6 +120,7 @@ const paginationConfig = reactive({
|
|||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('container/network/index', { search: searchName });
|
||||||
|
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
const isActive = ref(false);
|
const isActive = ref(false);
|
||||||
@@ -138,15 +141,10 @@ const onClean = () => {
|
|||||||
type: 'info',
|
type: 'info',
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
let params = {
|
await cleanNetworks()
|
||||||
taskID: newUUID(),
|
.then((res) => {
|
||||||
pruneType: 'network',
|
|
||||||
withTagAll: false,
|
|
||||||
};
|
|
||||||
await containerPrune(params)
|
|
||||||
.then(() => {
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
openTaskLog(params.taskID);
|
openTaskLog(res.data.taskID);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import OperatorDialog from '@/views/container/repo/operator/index.vue';
|
import OperatorDialog from '@/views/container/repo/operator/index.vue';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { dateFormat } from '@/utils/date';
|
import { dateFormat } from '@/utils/date';
|
||||||
@@ -77,6 +78,7 @@ const paginationConfig = reactive({
|
|||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('container/repo/index', { search: searchName });
|
||||||
|
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
const confirmDialog = ref();
|
const confirmDialog = ref();
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [searchName]"
|
||||||
|
row-key="id"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
v-model:selects="selects"
|
v-model:selects="selects"
|
||||||
:data="data"
|
:data="data"
|
||||||
@@ -79,6 +81,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { dateFormat, getCurrentDateFormatted } from '@/utils/date';
|
import { dateFormat, getCurrentDateFormatted } from '@/utils/date';
|
||||||
import { downloadWithContent } from '@/utils/file';
|
import { downloadWithContent } from '@/utils/file';
|
||||||
@@ -108,6 +111,7 @@ const paginationConfig = reactive({
|
|||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('container/template/index', { search: searchName });
|
||||||
|
|
||||||
const search = async () => {
|
const search = async () => {
|
||||||
if (!isActive.value || !isExist.value) {
|
if (!isActive.value || !isExist.value) {
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [searchName]"
|
||||||
|
row-key="name"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
v-model:selects="selects"
|
v-model:selects="selects"
|
||||||
:data="data"
|
:data="data"
|
||||||
@@ -122,6 +124,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import CreateDialog from '@/views/container/volume/create/index.vue';
|
import CreateDialog from '@/views/container/volume/create/index.vue';
|
||||||
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
|
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
|
||||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||||
@@ -155,6 +158,7 @@ const paginationConfig = reactive({
|
|||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('container/volume/index', { search: searchName });
|
||||||
const isActive = ref(false);
|
const isActive = ref(false);
|
||||||
const isExist = ref(false);
|
const isExist = ref(false);
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [searchName, searchGroupID]"
|
||||||
|
row-key="id"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
:default-sort="
|
:default-sort="
|
||||||
paginationConfig.order !== 'null'
|
paginationConfig.order !== 'null'
|
||||||
@@ -270,6 +272,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import Records from '@/views/cronjob/cronjob/record/index.vue';
|
import Records from '@/views/cronjob/cronjob/record/index.vue';
|
||||||
import Backups from '@/views/cronjob/cronjob/backup/index.vue';
|
import Backups from '@/views/cronjob/cronjob/backup/index.vue';
|
||||||
import Import from '@/views/cronjob/cronjob/import/index.vue';
|
import Import from '@/views/cronjob/cronjob/import/index.vue';
|
||||||
@@ -329,6 +332,7 @@ const pageState = usePageState(() => ({
|
|||||||
}));
|
}));
|
||||||
const paginationConfig = pageState.paginationConfig;
|
const paginationConfig = pageState.paginationConfig;
|
||||||
const { defaultGroupID, searchName, searchGroupID } = toRefs(pageState);
|
const { defaultGroupID, searchName, searchGroupID } = toRefs(pageState);
|
||||||
|
useSearchPersistence('cronjob/cronjob/index', { search: searchName });
|
||||||
|
|
||||||
const search = async (column?: any) => {
|
const search = async (column?: any) => {
|
||||||
if (column) {
|
if (column) {
|
||||||
|
|||||||
@@ -60,6 +60,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [searchInfo, group]"
|
||||||
|
row-key="id"
|
||||||
v-model:selects="selects"
|
v-model:selects="selects"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
:data="data"
|
:data="data"
|
||||||
@@ -130,6 +132,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { dateFormat, getCurrentDateFormatted } from '@/utils/date';
|
import { dateFormat, getCurrentDateFormatted } from '@/utils/date';
|
||||||
import { deepCopy } from '@/utils/misc';
|
import { deepCopy } from '@/utils/misc';
|
||||||
import { newUUID } from '@/utils/id';
|
import { newUUID } from '@/utils/id';
|
||||||
@@ -167,6 +170,7 @@ const paginationConfig = reactive({
|
|||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const searchInfo = ref<string>('');
|
const searchInfo = ref<string>('');
|
||||||
|
useSearchPersistence('cronjob/library/index', { search: searchInfo });
|
||||||
const group = ref<string>('');
|
const group = ref<string>('');
|
||||||
const groupOptions = ref();
|
const groupOptions = ref();
|
||||||
|
|
||||||
|
|||||||
@@ -323,6 +323,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { nextTick, onMounted, reactive, ref } from 'vue';
|
import { nextTick, onMounted, reactive, ref } from 'vue';
|
||||||
import { dateFormat } from '@/utils/date';
|
import { dateFormat } from '@/utils/date';
|
||||||
import { getRandomStr } from '@/utils/id';
|
import { getRandomStr } from '@/utils/id';
|
||||||
@@ -365,6 +366,7 @@ const loading = ref(false);
|
|||||||
const maskShow = ref(true);
|
const maskShow = ref(true);
|
||||||
const submitLoading = ref(false);
|
const submitLoading = ref(false);
|
||||||
const searchName = ref('');
|
const searchName = ref('');
|
||||||
|
useSearchPersistence('database/mongodb/index', { search: searchName });
|
||||||
const createVisible = ref(false);
|
const createVisible = ref(false);
|
||||||
const appStatusRef = ref();
|
const appStatusRef = ref();
|
||||||
const bindRef = ref();
|
const bindRef = ref();
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { dateFormat } from '@/utils/date';
|
import { dateFormat } from '@/utils/date';
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||||
@@ -100,6 +101,7 @@ const paginationConfig = reactive({
|
|||||||
order: 'null',
|
order: 'null',
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('database/mongodb/remote/index', { search: searchName });
|
||||||
|
|
||||||
const search = async (column?: any) => {
|
const search = async (column?: any) => {
|
||||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||||
|
|||||||
@@ -226,6 +226,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import BindDialog from '@/views/database/mysql/bind/index.vue';
|
import BindDialog from '@/views/database/mysql/bind/index.vue';
|
||||||
import UserDialog from '@/views/database/mysql/user/index.vue';
|
import UserDialog from '@/views/database/mysql/user/index.vue';
|
||||||
import OperateDialog from '@/views/database/mysql/create/index.vue';
|
import OperateDialog from '@/views/database/mysql/create/index.vue';
|
||||||
@@ -300,6 +301,7 @@ const paginationConfig = reactive({
|
|||||||
order: 'null',
|
order: 'null',
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('database/mysql/index', { search: searchName });
|
||||||
|
|
||||||
const mysqlContainer = ref();
|
const mysqlContainer = ref();
|
||||||
const mysqlStatus = ref();
|
const mysqlStatus = ref();
|
||||||
|
|||||||
@@ -74,6 +74,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { dateFormat } from '@/utils/date';
|
import { dateFormat } from '@/utils/date';
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||||
@@ -99,6 +100,7 @@ const paginationConfig = reactive({
|
|||||||
order: 'null',
|
order: 'null',
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('database/mysql/remote/index', { search: searchName });
|
||||||
|
|
||||||
const search = async (column?: any) => {
|
const search = async (column?: any) => {
|
||||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||||
|
|||||||
@@ -241,6 +241,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import BindDialog from '@/views/database/postgresql/bind/index.vue';
|
import BindDialog from '@/views/database/postgresql/bind/index.vue';
|
||||||
import OperateDialog from '@/views/database/postgresql/create/index.vue';
|
import OperateDialog from '@/views/database/postgresql/create/index.vue';
|
||||||
import DeleteDialog from '@/views/database/postgresql/delete/index.vue';
|
import DeleteDialog from '@/views/database/postgresql/delete/index.vue';
|
||||||
@@ -312,6 +313,7 @@ const paginationConfig = reactive({
|
|||||||
order: 'null',
|
order: 'null',
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('database/postgresql/index', { search: searchName });
|
||||||
|
|
||||||
const postgresqlContainer = ref();
|
const postgresqlContainer = ref();
|
||||||
const postgresqlStatus = ref();
|
const postgresqlStatus = ref();
|
||||||
|
|||||||
@@ -74,6 +74,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { dateFormat } from '@/utils/date';
|
import { dateFormat } from '@/utils/date';
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||||
@@ -99,6 +100,7 @@ const paginationConfig = reactive({
|
|||||||
order: 'null',
|
order: 'null',
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('database/postgresql/remote/index', { search: searchName });
|
||||||
|
|
||||||
const search = async (column?: any) => {
|
const search = async (column?: any) => {
|
||||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||||
|
|||||||
@@ -81,8 +81,23 @@
|
|||||||
:style="{ height: `calc(100vh - ${loadHeight()})`, 'background-color': '#000' }"
|
:style="{ height: `calc(100vh - ${loadHeight()})`, 'background-color': '#000' }"
|
||||||
:description="loadErrMsg()"
|
:description="loadErrMsg()"
|
||||||
>
|
>
|
||||||
<el-button v-if="currentDB.from === 'remote'" v-permission type="primary" @click="installCli">
|
<el-button
|
||||||
{{ $t('commons.button.enable') }}
|
v-if="currentDB.from === 'remote'"
|
||||||
|
v-permission
|
||||||
|
type="primary"
|
||||||
|
:loading="cliSubmitting"
|
||||||
|
:disabled="!cliStatusLoaded || cliInstalling"
|
||||||
|
@click="installCli"
|
||||||
|
>
|
||||||
|
{{ cliInstalling ? $t('database.redisCliEnabling') : $t('commons.button.enable') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="currentDB.from === 'remote' && cliTaskID && (cliInstalling || redisCliExist)"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openCliTaskLog"
|
||||||
|
>
|
||||||
|
{{ $t('commons.button.log') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-empty>
|
</el-empty>
|
||||||
<div>
|
<div>
|
||||||
@@ -134,6 +149,7 @@
|
|||||||
</DialogPro>
|
</DialogPro>
|
||||||
|
|
||||||
<QuickCmd ref="dialogQuickCmdRef" @reload="loadQuickCmd" />
|
<QuickCmd ref="dialogQuickCmdRef" @reload="loadQuickCmd" />
|
||||||
|
<TaskLog ref="cliTaskLogRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -143,17 +159,21 @@ import Conn from '@/views/database/redis/conn/index.vue';
|
|||||||
import Terminal from '@/components/terminal/index.vue';
|
import Terminal from '@/components/terminal/index.vue';
|
||||||
import AppStatus from '@/components/app-status/index.vue';
|
import AppStatus from '@/components/app-status/index.vue';
|
||||||
import QuickCmd from '@/views/database/redis/command/index.vue';
|
import QuickCmd from '@/views/database/redis/command/index.vue';
|
||||||
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||||
import { checkAppInstalled } from '@/api/modules/app';
|
import { checkAppInstalled } from '@/api/modules/app';
|
||||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||||
import { listDatabases, checkRedisCli, installRedisCli } from '@/api/modules/database';
|
import { listDatabases, loadRedisCliStatus, installRedisCli } from '@/api/modules/database';
|
||||||
import { Database } from '@/api/interface/database';
|
import { Database } from '@/api/interface/database';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { getCommandList } from '@/api/modules/command';
|
import { getCommandList } from '@/api/modules/command';
|
||||||
import { routerToName, routerToNameWithQuery } from '@/utils/router';
|
import { routerToName, routerToNameWithQuery } from '@/utils/router';
|
||||||
import { useOperateNodeContext } from '@/composables/useOperateNodeContext';
|
import { useOperateNodeContext } from '@/composables/useOperateNodeContext';
|
||||||
|
|
||||||
|
import TaskLog from '@/components/log/task/index.vue';
|
||||||
|
import bus from '@/global/bus';
|
||||||
|
import { newUUID } from '@/utils/id';
|
||||||
|
|
||||||
const { currentNode, currentRedisDB, openMenuTabs } = useGlobalStore();
|
const { currentNode, currentRedisDB, openMenuTabs } = useGlobalStore();
|
||||||
useOperateNodeContext(currentNode);
|
useOperateNodeContext(currentNode);
|
||||||
|
|
||||||
@@ -170,7 +190,21 @@ const appStatusRef = ref();
|
|||||||
|
|
||||||
const open = ref(false);
|
const open = ref(false);
|
||||||
|
|
||||||
const redisCliExist = ref();
|
const redisCliExist = ref(false);
|
||||||
|
const cliSubmitting = ref(false);
|
||||||
|
const cliStatusLoaded = ref(false);
|
||||||
|
const cliTaskID = ref('');
|
||||||
|
const cliTaskStatus = ref('');
|
||||||
|
const cliTaskLogRef = ref<InstanceType<typeof TaskLog>>();
|
||||||
|
const cliInstalling = computed(() => cliTaskStatus.value === 'Executing');
|
||||||
|
const cliStatusText = computed(() => {
|
||||||
|
if (cliInstalling.value) return i18n.global.t('database.redisCliEnabling');
|
||||||
|
if (cliTaskStatus.value === 'Failed') return i18n.global.t('database.redisCliEnableFailed');
|
||||||
|
return redisCliExist.value ? i18n.global.t('database.redisCliEnabled') : i18n.global.t('database.redisCliHelper');
|
||||||
|
});
|
||||||
|
let cliPollTimer: ReturnType<typeof setTimeout>;
|
||||||
|
let cliGeneration = 0;
|
||||||
|
let disposed = false;
|
||||||
|
|
||||||
const appKey = ref('redis');
|
const appKey = ref('redis');
|
||||||
const appName = ref();
|
const appName = ref();
|
||||||
@@ -248,8 +282,10 @@ const changeDatabase = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadDBOptions = async () => {
|
const loadDBOptions = async () => {
|
||||||
|
const generation = cliGeneration;
|
||||||
try {
|
try {
|
||||||
const res = await listDatabases('redis,redis-cluster');
|
const res = await listDatabases('redis,redis-cluster');
|
||||||
|
if (disposed || generation !== cliGeneration) return;
|
||||||
let datas = res.data || [];
|
let datas = res.data || [];
|
||||||
dbOptionsLocal.value = [];
|
dbOptionsLocal.value = [];
|
||||||
dbOptionsRemote.value = [];
|
dbOptionsRemote.value = [];
|
||||||
@@ -286,14 +322,14 @@ const loadDBOptions = async () => {
|
|||||||
reOpenTerminal();
|
reOpenTerminal();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isLoaded.value = true;
|
if (!disposed && generation === cliGeneration) isLoaded.value = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadErrMsg = () => {
|
const loadErrMsg = () => {
|
||||||
return currentDB.value.from === 'local'
|
return currentDB.value.from === 'local'
|
||||||
? i18n.global.t('commons.service.serviceNotStarted', ['Redis'])
|
? i18n.global.t('commons.service.serviceNotStarted', ['Redis'])
|
||||||
: i18n.global.t('database.redisCliHelper');
|
: cliStatusText.value;
|
||||||
};
|
};
|
||||||
const reOpenTerminal = async () => {
|
const reOpenTerminal = async () => {
|
||||||
closeTerminal(false);
|
closeTerminal(false);
|
||||||
@@ -301,6 +337,10 @@ const reOpenTerminal = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const initTerminal = async () => {
|
const initTerminal = async () => {
|
||||||
|
if (disposed || !currentDB.value) return;
|
||||||
|
const generation = cliGeneration;
|
||||||
|
const database = currentDBName.value;
|
||||||
|
const isCurrent = () => !disposed && generation === cliGeneration && database === currentDBName.value;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
if (currentDB.value.from === 'remote') {
|
if (currentDB.value.from === 'remote') {
|
||||||
if (!redisCliExist.value) {
|
if (!redisCliExist.value) {
|
||||||
@@ -311,9 +351,10 @@ const initTerminal = async () => {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
redisIsExist.value = true;
|
redisIsExist.value = true;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
if (!isCurrent()) return;
|
||||||
terminalShow.value = true;
|
terminalShow.value = true;
|
||||||
redisStatus.value = 'Running';
|
redisStatus.value = 'Running';
|
||||||
terminalRef.value.acceptParams({
|
terminalRef.value?.acceptParams({
|
||||||
endpoint: '/api/v2/hosts/terminal/container',
|
endpoint: '/api/v2/hosts/terminal/container',
|
||||||
args: `source=redis&name=${currentDBName.value}&from=${currentDB.value.from}`,
|
args: `source=redis&name=${currentDBName.value}&from=${currentDB.value.from}`,
|
||||||
error: '',
|
error: '',
|
||||||
@@ -325,13 +366,15 @@ const initTerminal = async () => {
|
|||||||
}
|
}
|
||||||
await checkAppInstalled(currentDB.value.type, currentDBName.value)
|
await checkAppInstalled(currentDB.value.type, currentDBName.value)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
if (!isCurrent()) return;
|
||||||
redisIsExist.value = res.data.isExist;
|
redisIsExist.value = res.data.isExist;
|
||||||
redisStatus.value = res.data.status;
|
redisStatus.value = res.data.status;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
if (!isCurrent()) return;
|
||||||
if (res.data.status === 'Running') {
|
if (res.data.status === 'Running') {
|
||||||
terminalShow.value = true;
|
terminalShow.value = true;
|
||||||
terminalRef.value.acceptParams({
|
terminalRef.value?.acceptParams({
|
||||||
endpoint: '/api/v2/hosts/terminal/container',
|
endpoint: '/api/v2/hosts/terminal/container',
|
||||||
args: `source=${currentDB.value.type}&name=${currentDBName.value}&from=${currentDB.value.from}`,
|
args: `source=${currentDB.value.type}&name=${currentDBName.value}&from=${currentDB.value.from}`,
|
||||||
error: '',
|
error: '',
|
||||||
@@ -342,6 +385,7 @@ const initTerminal = async () => {
|
|||||||
isRefresh.value = !isRefresh.value;
|
isRefresh.value = !isRefresh.value;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
if (!isCurrent()) return;
|
||||||
closeTerminal(false);
|
closeTerminal(false);
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
@@ -352,28 +396,78 @@ const closeTerminal = async (isKeepShow: boolean) => {
|
|||||||
terminalShow.value = isKeepShow;
|
terminalShow.value = isKeepShow;
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkCliValid = async () => {
|
const openCliTaskLog = () => {
|
||||||
await checkRedisCli()
|
if (cliTaskID.value) cliTaskLogRef.value?.openWithTaskID(cliTaskID.value, cliInstalling.value, currentNode.value);
|
||||||
.then((res) => {
|
|
||||||
redisCliExist.value = res.data;
|
|
||||||
loadDBOptions();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
loadDBOptions();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
const installCli = async () => {
|
|
||||||
loading.value = true;
|
const applyCliStatus = (status: Database.RedisCliStatus) => {
|
||||||
await installRedisCli()
|
const wasInstalling = cliInstalling.value;
|
||||||
.then(() => {
|
const wasInstalled = redisCliExist.value;
|
||||||
loading.value = false;
|
redisCliExist.value = status.installed;
|
||||||
redisCliExist.value = true;
|
cliTaskID.value = status.taskID;
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
cliTaskStatus.value = status.status;
|
||||||
|
cliStatusLoaded.value = true;
|
||||||
|
if (!wasInstalled && status.installed && !cliInstalling.value && currentDB.value?.from === 'remote') {
|
||||||
reOpenTerminal();
|
reOpenTerminal();
|
||||||
})
|
}
|
||||||
.catch(() => {
|
if (wasInstalling && !cliInstalling.value) {
|
||||||
loading.value = false;
|
bus.emit('refreshTask', true);
|
||||||
});
|
if (status.status === 'Success' && status.installed) {
|
||||||
|
MsgSuccess(i18n.global.t('database.redisCliEnabled'));
|
||||||
|
} else if (status.status === 'Failed') {
|
||||||
|
MsgError(i18n.global.t('database.redisCliEnableFailed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scheduleCliPoll = (generation: number) => {
|
||||||
|
clearTimeout(cliPollTimer);
|
||||||
|
if (disposed || generation !== cliGeneration) return;
|
||||||
|
cliPollTimer = setTimeout(() => refreshCliStatus(generation), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshCliStatus = async (generation: number) => {
|
||||||
|
try {
|
||||||
|
const res = await loadRedisCliStatus(currentNode.value);
|
||||||
|
if (disposed || generation !== cliGeneration) return;
|
||||||
|
applyCliStatus(res.data);
|
||||||
|
if (cliInstalling.value) scheduleCliPoll(generation);
|
||||||
|
} catch {
|
||||||
|
// Keep tracking a running task after a transient request failure.
|
||||||
|
if (!disposed && generation === cliGeneration) scheduleCliPoll(generation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkCliValid = async () => {
|
||||||
|
const generation = cliGeneration;
|
||||||
|
await refreshCliStatus(generation);
|
||||||
|
if (!disposed && generation === cliGeneration) await loadDBOptions();
|
||||||
|
};
|
||||||
|
|
||||||
|
const installCli = async () => {
|
||||||
|
if (cliSubmitting.value || cliInstalling.value) return;
|
||||||
|
const generation = cliGeneration;
|
||||||
|
const node = currentNode.value;
|
||||||
|
cliSubmitting.value = true;
|
||||||
|
clearTimeout(cliPollTimer);
|
||||||
|
try {
|
||||||
|
const res = await installRedisCli(newUUID(), node);
|
||||||
|
if (disposed || generation !== cliGeneration) return;
|
||||||
|
applyCliStatus(res.data);
|
||||||
|
bus.emit('refreshTask', true);
|
||||||
|
if (cliInstalling.value) {
|
||||||
|
MsgSuccess(i18n.global.t('database.redisCliTaskSubmitted'));
|
||||||
|
openCliTaskLog();
|
||||||
|
scheduleCliPoll(generation);
|
||||||
|
} else if (res.data.installed && currentDB.value?.from === 'remote') {
|
||||||
|
reOpenTerminal();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// The server may have accepted the task even if the response was lost.
|
||||||
|
if (!disposed && generation === cliGeneration) await refreshCliStatus(generation);
|
||||||
|
} finally {
|
||||||
|
if (!disposed && generation === cliGeneration) cliSubmitting.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadQuickCmd = async () => {
|
const loadQuickCmd = async () => {
|
||||||
@@ -392,6 +486,25 @@ const onSetQuickCmd = () => {
|
|||||||
dialogQuickCmdRef.value.acceptParams();
|
dialogQuickCmdRef.value.acceptParams();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(currentNode, () => {
|
||||||
|
cliGeneration++;
|
||||||
|
clearTimeout(cliPollTimer);
|
||||||
|
cliTaskLogRef.value?.handleClose();
|
||||||
|
closeTerminal(false);
|
||||||
|
redisCliExist.value = false;
|
||||||
|
cliSubmitting.value = false;
|
||||||
|
cliStatusLoaded.value = false;
|
||||||
|
cliTaskID.value = '';
|
||||||
|
cliTaskStatus.value = '';
|
||||||
|
currentDB.value = undefined;
|
||||||
|
redisStatus.value = undefined;
|
||||||
|
dbOptionsLocal.value = [];
|
||||||
|
dbOptionsRemote.value = [];
|
||||||
|
isLoaded.value = false;
|
||||||
|
loading.value = false;
|
||||||
|
checkCliValid();
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadQuickCmd();
|
loadQuickCmd();
|
||||||
checkCliValid();
|
checkCliValid();
|
||||||
@@ -403,6 +516,9 @@ const onAfter = () => {
|
|||||||
initTerminal();
|
initTerminal();
|
||||||
};
|
};
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
disposed = true;
|
||||||
|
cliGeneration++;
|
||||||
|
clearTimeout(cliPollTimer);
|
||||||
closeTerminal(false);
|
closeTerminal(false);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { dateFormat } from '@/utils/date';
|
import { dateFormat } from '@/utils/date';
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
import { deleteCheckDatabase, searchDatabases } from '@/api/modules/database';
|
||||||
@@ -98,6 +99,7 @@ const paginationConfig = reactive({
|
|||||||
order: 'null',
|
order: 'null',
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('database/redis/remote/index', { search: searchName });
|
||||||
|
|
||||||
const search = async (column?: any) => {
|
const search = async (column?: any) => {
|
||||||
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<table style="width: 100%" class="tab-table">
|
<table style="width: 100%" class="tab-table">
|
||||||
|
<tbody>
|
||||||
<tr v-for="(row, index) in form.saves" :key="index">
|
<tr v-for="(row, index) in form.saves" :key="index">
|
||||||
<td width="32%">
|
<td width="32%">
|
||||||
<el-input type="number" v-model="row.second"></el-input>
|
<el-input type="number" v-model="row.second"></el-input>
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div>
|
<div>
|
||||||
<span style="margin-left: 2px; margin-top: 5px">{{ $t('database.rdbHelper3') }}</span>
|
<span style="margin-left: 2px; margin-top: 5px">{{ $t('database.rdbHelper3') }}</span>
|
||||||
|
|||||||
@@ -739,7 +739,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
|
import { toRef, computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||||
import {
|
import {
|
||||||
addFavorite,
|
addFavorite,
|
||||||
batchGetFileRemarks,
|
batchGetFileRemarks,
|
||||||
@@ -852,6 +853,7 @@ const initData = () => ({
|
|||||||
sortOrder: 'ascending',
|
sortOrder: 'ascending',
|
||||||
});
|
});
|
||||||
let req = reactive(initData());
|
let req = reactive(initData());
|
||||||
|
useSearchPersistence('host/file-management/index', { search: toRef(req, 'search') });
|
||||||
let loading = ref(false);
|
let loading = ref(false);
|
||||||
const paths = ref<FilePaths[]>([]);
|
const paths = ref<FilePaths[]>([]);
|
||||||
const breadcrumbVisiblePaths = ref<FilePaths[]>([]);
|
const breadcrumbVisiblePaths = ref<FilePaths[]>([]);
|
||||||
|
|||||||
@@ -232,6 +232,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { computed, reactive, ref, watch } from 'vue';
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
import FireRouter from '@/views/host/firewall/index.vue';
|
import FireRouter from '@/views/host/firewall/index.vue';
|
||||||
import DockerGuardStatus from '@/views/host/firewall/docker/status/index.vue';
|
import DockerGuardStatus from '@/views/host/firewall/docker/status/index.vue';
|
||||||
@@ -274,6 +275,7 @@ const taskLogRef = ref<InstanceType<typeof TaskLog>>();
|
|||||||
const openRuleTask = (taskID: string) => taskLogRef.value?.openWithTaskID(taskID, true);
|
const openRuleTask = (taskID: string) => taskLogRef.value?.openWithTaskID(taskID, true);
|
||||||
const orphanDrawerVisible = ref(false);
|
const orphanDrawerVisible = ref(false);
|
||||||
const searchName = ref('');
|
const searchName = ref('');
|
||||||
|
useSearchPersistence('host/firewall/docker/index', { search: searchName });
|
||||||
const selects = ref<Firewall.DockerGuardContainer[]>([]);
|
const selects = ref<Firewall.DockerGuardContainer[]>([]);
|
||||||
const orphanSelects = ref<Firewall.DockerGuardEndpoint[]>([]);
|
const orphanSelects = ref<Firewall.DockerGuardEndpoint[]>([]);
|
||||||
const data = reactive<Firewall.DockerGuardList>({
|
const data = reactive<Firewall.DockerGuardList>({
|
||||||
|
|||||||
@@ -57,6 +57,18 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [searchName, fireName]"
|
||||||
|
:row-key="
|
||||||
|
(row) =>
|
||||||
|
JSON.stringify([
|
||||||
|
row.family,
|
||||||
|
row.protocol,
|
||||||
|
row.port,
|
||||||
|
row.targetIP,
|
||||||
|
row.targetPort,
|
||||||
|
row.interface,
|
||||||
|
])
|
||||||
|
"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
v-model:selects="selects"
|
v-model:selects="selects"
|
||||||
@search="search"
|
@search="search"
|
||||||
@@ -133,6 +145,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import OperateDialog from './operate/index.vue';
|
import OperateDialog from './operate/index.vue';
|
||||||
import ImportDialog from './import/index.vue';
|
import ImportDialog from './import/index.vue';
|
||||||
import RuleSync from '@/views/host/firewall/sync/index.vue';
|
import RuleSync from '@/views/host/firewall/sync/index.vue';
|
||||||
@@ -153,6 +166,7 @@ import { ElMessageBox } from 'element-plus';
|
|||||||
const loading = ref();
|
const loading = ref();
|
||||||
const selects = ref<any>([]);
|
const selects = ref<any>([]);
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('host/firewall/forward/index', { search: searchName });
|
||||||
|
|
||||||
const isInit = ref(false);
|
const isInit = ref(false);
|
||||||
const isBind = ref(false);
|
const isBind = ref(false);
|
||||||
|
|||||||
@@ -185,6 +185,7 @@
|
|||||||
<template #main>
|
<template #main>
|
||||||
<div v-loading="loading">
|
<div v-loading="loading">
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [searchName, selectedRuleFilters, visibleIptablesChains]"
|
||||||
v-model:selects="selects"
|
v-model:selects="selects"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
:data="allRows"
|
:data="allRows"
|
||||||
@@ -397,6 +398,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { Firewall } from '@/api/interface/firewall';
|
import { Firewall } from '@/api/interface/firewall';
|
||||||
import { Process } from '@/api/interface/process';
|
import { Process } from '@/api/interface/process';
|
||||||
import {
|
import {
|
||||||
@@ -516,6 +518,7 @@ const visibleIptablesChains = ref<string[]>(
|
|||||||
loadCachedFilterValues(chainFilterStorageKey, iptablesChains, ['1PANEL_BASIC']),
|
loadCachedFilterValues(chainFilterStorageKey, iptablesChains, ['1PANEL_BASIC']),
|
||||||
);
|
);
|
||||||
const searchName = ref('');
|
const searchName = ref('');
|
||||||
|
useSearchPersistence('host/firewall/rule/index', { search: searchName });
|
||||||
const inventoryItems = ref<Firewall.InventoryItem[]>([]);
|
const inventoryItems = ref<Firewall.InventoryItem[]>([]);
|
||||||
const positionRanges = ref<Partial<Record<Firewall.Family, Firewall.PositionRange>>>({});
|
const positionRanges = ref<Partial<Record<Firewall.Family, Firewall.PositionRange>>>({});
|
||||||
const inventoryTotal = ref(0);
|
const inventoryTotal = ref(0);
|
||||||
@@ -659,7 +662,6 @@ const loadRules = async (refreshUsage: boolean) => {
|
|||||||
paginationConfig.total = total;
|
paginationConfig.total = total;
|
||||||
inventoryTotal.value = response.data.allTotal || 0;
|
inventoryTotal.value = response.data.allTotal || 0;
|
||||||
managedTotal.value = response.data.managedTotal || 0;
|
managedTotal.value = response.data.managedTotal || 0;
|
||||||
selects.value = [];
|
|
||||||
} finally {
|
} finally {
|
||||||
if (requestID === searchRequestID) loading.value = false;
|
if (requestID === searchRequestID) loading.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||||
import { dateFormatServerTimezone } from '@/utils/date';
|
import { dateFormatServerTimezone } from '@/utils/date';
|
||||||
import { downloadFile } from '@/utils/file';
|
import { downloadFile } from '@/utils/file';
|
||||||
@@ -119,6 +120,7 @@ const exportConfig = reactive({
|
|||||||
status: 'All',
|
status: 'All',
|
||||||
});
|
});
|
||||||
const searchInfo = ref();
|
const searchInfo = ref();
|
||||||
|
useSearchPersistence('host/ssh/log/log', { search: searchInfo });
|
||||||
const searchStatus = ref('All');
|
const searchStatus = ref('All');
|
||||||
|
|
||||||
const search = async () => {
|
const search = async () => {
|
||||||
|
|||||||
@@ -24,8 +24,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import FireRouter from '@/views/host/ssh/index.vue';
|
import FireRouter from '@/views/host/ssh/index.vue';
|
||||||
import { ref, onMounted, onUnmounted, reactive } from 'vue';
|
import { toRef, ref, onMounted, onUnmounted, reactive } from 'vue';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { stopProcess } from '@/api/modules/process';
|
import { stopProcess } from '@/api/modules/process';
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
@@ -37,6 +38,7 @@ const sshSearch = reactive({
|
|||||||
type: 'ssh',
|
type: 'ssh',
|
||||||
loginUser: '',
|
loginUser: '',
|
||||||
});
|
});
|
||||||
|
useSearchPersistence('host/ssh/session/index', { search: toRef(sshSearch, 'loginUser') });
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -101,6 +101,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||||
import { Log } from '@/api/interface/log';
|
import { Log } from '@/api/interface/log';
|
||||||
import { getSystemLogStatus, listRunningServices, readSystemLogs } from '@/api/modules/log';
|
import { getSystemLogStatus, listRunningServices, readSystemLogs } from '@/api/modules/log';
|
||||||
@@ -113,6 +114,7 @@ const logs = ref<Log.SystemLogItem[]>([]);
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const watching = ref(false);
|
const watching = ref(false);
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
|
useSearchPersistence('log/host-system/index', { keyword });
|
||||||
const priority = ref('');
|
const priority = ref('');
|
||||||
const service = ref('');
|
const service = ref('');
|
||||||
const services = ref<string[]>([]);
|
const services = ref<string[]>([]);
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||||
import LogRouter from '@/views/log/router/index.vue';
|
import LogRouter from '@/views/log/router/index.vue';
|
||||||
import { dateFormat } from '@/utils/date';
|
import { dateFormat } from '@/utils/date';
|
||||||
@@ -66,6 +67,7 @@ const paginationConfig = reactive({
|
|||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const searchInfo = ref<string>('');
|
const searchInfo = ref<string>('');
|
||||||
|
useSearchPersistence('log/login/index', { search: searchInfo });
|
||||||
const searchStatus = ref<string>('');
|
const searchStatus = ref<string>('');
|
||||||
|
|
||||||
const search = async () => {
|
const search = async () => {
|
||||||
|
|||||||
@@ -125,6 +125,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||||
import LogRouter from '@/views/log/router/index.vue';
|
import LogRouter from '@/views/log/router/index.vue';
|
||||||
import { dateFormat } from '@/utils/date';
|
import { dateFormat } from '@/utils/date';
|
||||||
@@ -145,6 +146,7 @@ const paginationConfig = reactive({
|
|||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const searchName = ref<string>('');
|
const searchName = ref<string>('');
|
||||||
|
useSearchPersistence('log/operation/index', { search: searchName });
|
||||||
const searchGroup = ref<string>('');
|
const searchGroup = ref<string>('');
|
||||||
const searchStatus = ref<string>('');
|
const searchStatus = ref<string>('');
|
||||||
const searchNode = ref<string>('');
|
const searchNode = ref<string>('');
|
||||||
|
|||||||
@@ -120,8 +120,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { dateFormat } from '@/utils/date';
|
import { dateFormat } from '@/utils/date';
|
||||||
import { onMounted, ref } from 'vue';
|
import { toRef, onMounted, ref } from 'vue';
|
||||||
import { searchBackup, deleteBackup, refreshToken } from '@/api/modules/backup';
|
import { searchBackup, deleteBackup, refreshToken } from '@/api/modules/backup';
|
||||||
import Operate from '@/views/setting/backup-account/operate/index.vue';
|
import Operate from '@/views/setting/backup-account/operate/index.vue';
|
||||||
import DetailShow from '@/components/detail-show/index.vue';
|
import DetailShow from '@/components/detail-show/index.vue';
|
||||||
@@ -142,6 +143,7 @@ const paginationConfig = reactive({
|
|||||||
type: '',
|
type: '',
|
||||||
name: '',
|
name: '',
|
||||||
});
|
});
|
||||||
|
useSearchPersistence('setting/backup-account/index', { search: toRef(paginationConfig, 'name') });
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
const dialogRef = ref();
|
const dialogRef = ref();
|
||||||
const detailRef = ref();
|
const detailRef = ref();
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [searchName]"
|
||||||
|
row-key="id"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
v-model:selects="selects"
|
v-model:selects="selects"
|
||||||
:data="data"
|
:data="data"
|
||||||
@@ -223,6 +225,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import {
|
import {
|
||||||
searchSnapshotPage,
|
searchSnapshotPage,
|
||||||
snapshotDelete,
|
snapshotDelete,
|
||||||
@@ -258,6 +261,7 @@ const paginationConfig = reactive({
|
|||||||
order: 'null',
|
order: 'null',
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('setting/snapshot/index', { search: searchName });
|
||||||
|
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { Command } from '@/api/interface/command';
|
import { Command } from '@/api/interface/command';
|
||||||
import GroupDialog from '@/components/group/index.vue';
|
import GroupDialog from '@/components/group/index.vue';
|
||||||
import OperateDialog from '@/views/terminal/command/operate/index.vue';
|
import OperateDialog from '@/views/terminal/command/operate/index.vue';
|
||||||
@@ -123,6 +124,7 @@ const paginationConfig = reactive({
|
|||||||
order: 'ascending',
|
order: 'ascending',
|
||||||
});
|
});
|
||||||
const info = ref();
|
const info = ref();
|
||||||
|
useSearchPersistence('terminal/command/index', { search: info });
|
||||||
const group = ref<string>('');
|
const group = ref<string>('');
|
||||||
const dialogRef = ref();
|
const dialogRef = ref();
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
|
|||||||
@@ -79,6 +79,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import GroupDialog from '@/components/agent-group/index.vue';
|
import GroupDialog from '@/components/agent-group/index.vue';
|
||||||
import OperateDialog from '@/views/terminal/host/operate/index.vue';
|
import OperateDialog from '@/views/terminal/host/operate/index.vue';
|
||||||
import { deleteHost, editHostGroup, searchHosts } from '@/api/modules/terminal';
|
import { deleteHost, editHostGroup, searchHosts } from '@/api/modules/terminal';
|
||||||
@@ -99,6 +100,7 @@ const paginationConfig = reactive({
|
|||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const info = ref();
|
const info = ref();
|
||||||
|
useSearchPersistence('terminal/host/index', { search: info });
|
||||||
const group = ref<string>('');
|
const group = ref<string>('');
|
||||||
|
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
|
|||||||
@@ -49,6 +49,8 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
<template #main v-if="clamStatus.isExist">
|
<template #main v-if="clamStatus.isExist">
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [searchName]"
|
||||||
|
row-key="id"
|
||||||
:class="{ mask: !clamStatus.isRunning }"
|
:class="{ mask: !clamStatus.isRunning }"
|
||||||
v-if="!isSettingShow"
|
v-if="!isSettingShow"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
@@ -188,6 +190,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
@@ -215,6 +218,7 @@ const paginationConfig = reactive({
|
|||||||
order: 'null',
|
order: 'null',
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('toolbox/clam/index', { search: searchName });
|
||||||
|
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
const dialogRef = ref();
|
const dialogRef = ref();
|
||||||
|
|||||||
@@ -174,6 +174,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
@@ -200,6 +201,7 @@ const paginationConfig = reactive({
|
|||||||
order: 'null',
|
order: 'null',
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
useSearchPersistence('toolbox/ftp/index', { search: searchName });
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { Runtime } from '@/api/interface/runtime';
|
import { Runtime } from '@/api/interface/runtime';
|
||||||
import { GetPHPExtensions, InstallPHPExtension, UnInstallPHPExtension } from '@/api/modules/runtime';
|
import { GetPHPExtensions, InstallPHPExtension, UnInstallPHPExtension } from '@/api/modules/runtime';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
@@ -48,6 +49,7 @@ const supportExtensions = ref([]);
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const taskLogRef = ref();
|
const taskLogRef = ref();
|
||||||
const searchName = ref('');
|
const searchName = ref('');
|
||||||
|
useSearchPersistence('website/runtime/php/extension-management/index', { search: searchName });
|
||||||
const data = ref([]);
|
const data = ref([]);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
@@ -137,7 +139,6 @@ const searchByName = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const acceptParams = (req: Runtime.Runtime): void => {
|
const acceptParams = (req: Runtime.Runtime): void => {
|
||||||
searchName.value = '';
|
|
||||||
open.value = true;
|
open.value = true;
|
||||||
runtime.value = req;
|
runtime.value = req;
|
||||||
search();
|
search();
|
||||||
|
|||||||
@@ -221,7 +221,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
|
import { toRef, defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
||||||
import { deleteSSL, downloadFile, pushSSLToNode, searchSSL, updateSSL } from '@/api/modules/website';
|
import { deleteSSL, downloadFile, pushSSLToNode, searchSSL, updateSSL } from '@/api/modules/website';
|
||||||
import DnsAccount from './dns-account/index.vue';
|
import DnsAccount from './dns-account/index.vue';
|
||||||
import AcmeAccount from './acme-account/index.vue';
|
import AcmeAccount from './acme-account/index.vue';
|
||||||
@@ -286,6 +287,7 @@ const req = reactive({
|
|||||||
orderBy: 'updated_at',
|
orderBy: 'updated_at',
|
||||||
order: 'descending',
|
order: 'descending',
|
||||||
});
|
});
|
||||||
|
useSearchPersistence('website/ssl/index', { search: toRef(req, 'domain') });
|
||||||
const pushForm = ref({
|
const pushForm = ref({
|
||||||
id: 0,
|
id: 0,
|
||||||
pushNode: true,
|
pushNode: true,
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import { Website } from '@/api/interface/website';
|
import { Website } from '@/api/interface/website';
|
||||||
import { deleteTemplate, searchTemplates } from '@/api/modules/website';
|
import { deleteTemplate, searchTemplates } from '@/api/modules/website';
|
||||||
import TemplateOperate from '@/views/website/template/operate/index.vue';
|
import TemplateOperate from '@/views/website/template/operate/index.vue';
|
||||||
@@ -71,6 +72,7 @@ import { reactive, ref, onMounted } from 'vue';
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const data = ref<Website.Template[]>([]);
|
const data = ref<Website.Template[]>([]);
|
||||||
const searchName = ref('');
|
const searchName = ref('');
|
||||||
|
useSearchPersistence('website/template/index', { search: searchName });
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
const operateRef = ref();
|
const operateRef = ref();
|
||||||
const outputCreateRef = ref();
|
const outputCreateRef = ref();
|
||||||
|
|||||||
@@ -66,6 +66,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-if="!openNginxConfig" #main>
|
<template v-if="!openNginxConfig" #main>
|
||||||
<ComplexTable
|
<ComplexTable
|
||||||
|
:selection-context="() => [req.name, req.type, req.websiteGroupId]"
|
||||||
|
row-key="id"
|
||||||
:pagination-config="paginationConfig"
|
:pagination-config="paginationConfig"
|
||||||
:default-sort="tableSort.order ? tableSort : undefined"
|
:default-sort="tableSort.order ? tableSort : undefined"
|
||||||
v-model:view-mode="viewMode"
|
v-model:view-mode="viewMode"
|
||||||
@@ -344,6 +346,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useSearchPersistence } from '@/composables/useSearchPersistence';
|
||||||
import Backups from '@/components/backup/index.vue';
|
import Backups from '@/components/backup/index.vue';
|
||||||
import UploadDialog from '@/components/upload/index.vue';
|
import UploadDialog from '@/components/upload/index.vue';
|
||||||
import DefaultServer from '@/views/website/website/default/index.vue';
|
import DefaultServer from '@/views/website/website/default/index.vue';
|
||||||
@@ -358,7 +361,7 @@ import BatchSetGroup from '@/views/website/website/batch-op/group.vue';
|
|||||||
import BatchSetHttps from '@/views/website/website/batch-op/https.vue';
|
import BatchSetHttps from '@/views/website/website/batch-op/https.vue';
|
||||||
|
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { onMounted, reactive, ref, computed } from 'vue';
|
import { toRef, onMounted, reactive, ref, computed } from 'vue';
|
||||||
import { batchOperate, opWebsite, searchWebsites, updateWebsite } from '@/api/modules/website';
|
import { batchOperate, opWebsite, searchWebsites, updateWebsite } from '@/api/modules/website';
|
||||||
import { Website } from '@/api/interface/website';
|
import { Website } from '@/api/interface/website';
|
||||||
import { App } from '@/api/interface/app';
|
import { App } from '@/api/interface/app';
|
||||||
@@ -448,6 +451,7 @@ const pageState = usePageState(() => ({
|
|||||||
}));
|
}));
|
||||||
const paginationConfig = pageState.paginationConfig;
|
const paginationConfig = pageState.paginationConfig;
|
||||||
const req = pageState.req;
|
const req = pageState.req;
|
||||||
|
useSearchPersistence('website/website/index', { search: toRef(req, 'name') });
|
||||||
const tableSort = pageState.tableSort;
|
const tableSort = pageState.tableSort;
|
||||||
|
|
||||||
const goRouter = async (key: string) => {
|
const goRouter = async (key: string) => {
|
||||||
@@ -496,7 +500,6 @@ const search = async () => {
|
|||||||
req.pageSize = paginationConfig.pageSize;
|
req.pageSize = paginationConfig.pageSize;
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
data.value = [];
|
|
||||||
await searchWebsites(req)
|
await searchWebsites(req)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
data.value = res.data.items;
|
data.value = res.data.items;
|
||||||
|
|||||||
Reference in New Issue
Block a user