From dbf6d9e5fefd50f5da6c1f34284bb9c94d7b8d3c Mon Sep 17 00:00:00 2001 From: guoyuqi Date: Wed, 23 Sep 2026 16:25:00 +0800 Subject: [PATCH] fix: fix clean network --- agent/app/api/v2/container.go | 18 ++ agent/app/dto/container_network_cleanup.go | 19 ++ agent/app/service/container.go | 1 + .../app/service/container_network_cleanup.go | 72 +++++++ .../service/container_network_cleanup_test.go | 113 +++++++++++ agent/cmd/server/docs/x-log.json | 7 + agent/i18n/lang/en.yaml | 10 + agent/i18n/lang/zh-Hant.yaml | 10 + agent/i18n/lang/zh.yaml | 10 + agent/router/ro_container.go | 1 + agent/utils/docker/network_cleanup.go | 111 +++++++++++ agent/utils/docker/network_cleanup_test.go | 72 +++++++ core/cmd/server/docs/x-log.json | 7 + docs/container-network-cleanup.md | 33 ++++ frontend/package-lock.json | 183 +----------------- frontend/src/api/modules/container.ts | 3 + .../src/views/container/network/index.vue | 14 +- 17 files changed, 501 insertions(+), 183 deletions(-) create mode 100644 agent/app/dto/container_network_cleanup.go create mode 100644 agent/app/service/container_network_cleanup.go create mode 100644 agent/app/service/container_network_cleanup_test.go create mode 100644 agent/utils/docker/network_cleanup.go create mode 100644 agent/utils/docker/network_cleanup_test.go create mode 100644 docs/container-network-cleanup.md diff --git a/agent/app/api/v2/container.go b/agent/app/api/v2/container.go index 7f3786197..7f1b033a4 100644 --- a/agent/app/api/v2/container.go +++ b/agent/app/api/v2/container.go @@ -964,3 +964,21 @@ func (b *BaseApi) ContainerStreamLogs(c *gin.Context) { containerService.StreamLogs(c, streamLog) } + +// CleanNetworks submits a network cleanup task. +// @Tags Container Network +// @Summary Submit unused network cleanup task +// @Produce json +// @Success 200 {object} dto.NetworkCleanupTask +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/network/clean [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理未使用的容器网络","formatEN":"Clean unused container networks"} +func (b *BaseApi) CleanNetworks(c *gin.Context) { + result, err := containerService.CleanNetworks() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, result) +} diff --git a/agent/app/dto/container_network_cleanup.go b/agent/app/dto/container_network_cleanup.go new file mode 100644 index 000000000..425c70c35 --- /dev/null +++ b/agent/app/dto/container_network_cleanup.go @@ -0,0 +1,19 @@ +package dto + +// NetworkCleanupReport records partial success; failed entries were not deleted. +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"` +} + +// NetworkCleanupTask identifies an asynchronous task and its log. +type NetworkCleanupTask struct { + TaskID string `json:"taskID"` +} diff --git a/agent/app/service/container.go b/agent/app/service/container.go index d65ef4f1f..9866ba1cc 100644 --- a/agent/app/service/container.go +++ b/agent/app/service/container.go @@ -92,6 +92,7 @@ type IContainerService interface { Inspect(req dto.InspectReq) (string, error) DeleteNetwork(req dto.BatchDelete) error + CleanNetworks() (*dto.NetworkCleanupTask, error) CreateNetwork(req dto.NetworkCreate) error DeleteVolume(req dto.BatchDelete) error CreateVolume(req dto.VolumeCreate) error diff --git a/agent/app/service/container_network_cleanup.go b/agent/app/service/container_network_cleanup.go new file mode 100644 index 000000000..3f0b9e70c --- /dev/null +++ b/agent/app/service/container_network_cleanup.go @@ -0,0 +1,72 @@ +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 + +// CleanNetworks submits a task independent of the HTTP request lifetime. +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 +} diff --git a/agent/app/service/container_network_cleanup_test.go b/agent/app/service/container_network_cleanup_test.go new file mode 100644 index 000000000..89373f340 --- /dev/null +++ b/agent/app/service/container_network_cleanup_test.go @@ -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) + } + }) + } +} diff --git a/agent/cmd/server/docs/x-log.json b/agent/cmd/server/docs/x-log.json index 74d8f7072..b9f70d21d 100644 --- a/agent/cmd/server/docs/x-log.json +++ b/agent/cmd/server/docs/x-log.json @@ -644,6 +644,13 @@ "formatZH": "创建容器网络 name", "formatEN": "create container network [name]" }, + "/containers/network/clean": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清理未使用的容器网络", + "formatEN": "Clean unused container networks" + }, "/containers/network/del": { "bodyKeys": [ "names" diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 92c073333..ab910746d 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -730,3 +730,13 @@ FileTaskRename: 'Target filename: {{ .name }}' 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." 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" diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index 87fedfb24..3d1e33184 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -730,3 +730,13 @@ FileTaskRename: '目標檔名:{{ .name }}' ErrFirewallBackendCleanupRequired: "目前後端 {{ .current }} 中仍有 1Panel 規則,請先清理後再切換至 {{ .target }}。" ErrDockerIPv4ForwardingDisabled: "IPv4 轉送尚未啟用,請先設定 net.ipv4.ip_forward=1,再使用 Docker 防火牆後端。" 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: "部分網路清理失敗,請查看任務日誌" diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index a9e8129ea..cf5710182 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -730,3 +730,13 @@ FileTaskRename: '目标文件名:{{ .name }}' ErrFirewallBackendCleanupRequired: "当前后端 {{ .current }} 中仍有 1Panel 规则,请先清理后再切换到 {{ .target }}。" ErrDockerIPv4ForwardingDisabled: "IPv4 转发未开启,请先设置 net.ipv4.ip_forward=1,再使用 Docker 防火墙后端。" 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: "部分网络清理失败,请查看任务日志" diff --git a/agent/router/ro_container.go b/agent/router/ro_container.go index 8a715a18c..cba61e9c4 100644 --- a/agent/router/ro_container.go +++ b/agent/router/ro_container.go @@ -77,6 +77,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) { baRouter.GET("/network", baseApi.ListNetwork) baRouter.POST("/network/del", baseApi.DeleteNetwork) + baRouter.POST("/network/clean", baseApi.CleanNetworks) baRouter.POST("/network/search", baseApi.SearchNetwork) baRouter.POST("/network", baseApi.CreateNetwork) baRouter.GET("/volume", baseApi.ListVolume) diff --git a/agent/utils/docker/network_cleanup.go b/agent/utils/docker/network_cleanup.go new file mode 100644 index 000000000..81322396f --- /dev/null +++ b/agent/utils/docker/network_cleanup.go @@ -0,0 +1,111 @@ +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" +) + +// NetworkCleanupClient deliberately exposes no disconnect or force operation. +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 +} + +// CleanUnusedNetworks lists networks and all containers before deleting anything. +// Docker remains the final arbiter for connections racing this scan. +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 +} diff --git a/agent/utils/docker/network_cleanup_test.go b/agent/utils/docker/network_cleanup_test.go new file mode 100644 index 000000000..46e605c69 --- /dev/null +++ b/agent/utils/docker/network_cleanup_test.go @@ -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) + } +} diff --git a/core/cmd/server/docs/x-log.json b/core/cmd/server/docs/x-log.json index 74d8f7072..b9f70d21d 100644 --- a/core/cmd/server/docs/x-log.json +++ b/core/cmd/server/docs/x-log.json @@ -644,6 +644,13 @@ "formatZH": "创建容器网络 name", "formatEN": "create container network [name]" }, + "/containers/network/clean": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清理未使用的容器网络", + "formatEN": "Clean unused container networks" + }, "/containers/network/del": { "bodyKeys": [ "names" diff --git a/docs/container-network-cleanup.md b/docs/container-network-cleanup.md new file mode 100644 index 000000000..5db775897 --- /dev/null +++ b/docs/container-network-cleanup.md @@ -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`:任务持久化,维护状态,写入开始、结束标记;日志文件为 `/Container/.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 客户端和临时任务数据库,不执行真实网络删除。 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3d61d0797..051423e2a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -126,7 +126,6 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -1014,7 +1013,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1031,7 +1029,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1048,7 +1045,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1065,7 +1061,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1082,7 +1077,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1099,7 +1093,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1116,7 +1109,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1133,7 +1125,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1150,7 +1141,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1167,7 +1157,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1184,7 +1173,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1201,7 +1189,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1218,7 +1205,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1235,7 +1221,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1252,7 +1237,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1269,7 +1253,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1286,7 +1269,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1303,7 +1285,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1320,7 +1301,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1337,7 +1317,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1354,7 +1333,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1371,7 +1349,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1388,7 +1365,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1405,7 +1381,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1422,7 +1397,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1439,7 +1413,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1773,18 +1746,6 @@ "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": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -2013,7 +1974,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -2053,7 +2013,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2074,7 +2033,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2095,7 +2053,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2116,7 +2073,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2137,7 +2093,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2158,7 +2113,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2179,7 +2133,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2200,7 +2153,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2221,7 +2173,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2242,7 +2193,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2263,7 +2213,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2284,7 +2233,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2305,7 +2253,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2350,7 +2297,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2367,7 +2313,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2384,7 +2329,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2401,7 +2345,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2418,7 +2361,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2435,7 +2377,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2452,7 +2393,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2469,7 +2409,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2486,7 +2425,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2503,7 +2441,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2520,7 +2457,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2537,7 +2473,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2554,7 +2489,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2571,7 +2505,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2984,7 +2917,6 @@ "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/lodash": "*" } @@ -3016,9 +2948,8 @@ "version": "26.4.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz", "integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==", - "dev": true, + "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~8.3.0" } @@ -3321,7 +3252,6 @@ "integrity": "sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "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", "integrity": "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.4.0", @@ -4052,15 +3981,13 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4317,7 +4244,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4347,7 +4273,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", @@ -4688,7 +4613,6 @@ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", @@ -5373,7 +5297,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, "license": "Apache-2.0", "optional": true, "bin": { @@ -5766,7 +5689,6 @@ "devOptional": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -5828,7 +5750,6 @@ "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", "dev": true, "license": "MIT", - "peer": true, "workspaces": [ "packages/*" ], @@ -5888,7 +5809,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -6240,7 +6160,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -6342,7 +6261,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -6822,7 +6740,7 @@ "version": "5.1.9", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/imurmurhash": { @@ -6895,7 +6813,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6921,7 +6839,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -6966,7 +6884,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -7052,7 +6969,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -7743,15 +7660,13 @@ "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash-es": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash-unified": { "version": "1.0.3", @@ -7955,7 +7870,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.5.0", @@ -8253,7 +8167,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -8441,7 +8354,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, "license": "MIT", "optional": true }, @@ -8697,7 +8609,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -8722,7 +8633,6 @@ "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", "license": "MIT", - "peer": true, "dependencies": { "@vue/devtools-api": "^7.7.7" }, @@ -8803,7 +8713,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", @@ -8915,7 +8824,6 @@ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9284,7 +9192,6 @@ "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" @@ -9394,9 +9301,8 @@ "version": "1.103.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.103.1.tgz", "integrity": "sha512-9icZURbP51S6S0QGoyaeqk9uB06GNWxsFYWfH5RgpFgqK5FA8tJcM3AdVxrZEVJ7dz+L87nG95gBKf4VuaMHGw==", - "dev": true, + "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^5.0.0", "immutable": "^5.1.5", @@ -9622,29 +9528,6 @@ "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": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -9989,34 +9872,6 @@ "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": { "version": "1.9.0", "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", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -10110,7 +9964,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -10175,7 +10028,6 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10214,7 +10066,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/unimport": { @@ -10604,7 +10456,6 @@ "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", @@ -10879,7 +10730,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -10900,7 +10750,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -10921,7 +10770,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -10942,7 +10790,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -10963,7 +10810,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -10984,7 +10830,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11005,7 +10850,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11026,7 +10870,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11047,7 +10890,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11068,7 +10910,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11089,7 +10930,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11128,7 +10968,6 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz", "integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.42", "@vue/compiler-sfc": "3.5.42", @@ -11182,7 +11021,6 @@ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" @@ -11209,7 +11047,6 @@ "integrity": "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0 || ^9.0.0", diff --git a/frontend/src/api/modules/container.ts b/frontend/src/api/modules/container.ts index e5176cfa5..131f1c34e 100644 --- a/frontend/src/api/modules/container.ts +++ b/frontend/src/api/modules/container.ts @@ -164,6 +164,9 @@ export const searchNetwork = (params: SearchWithPage) => { export const listNetwork = () => { return http.get>(`/containers/network`); }; +export const cleanNetworks = () => { + return http.post<{ taskID: string }>(`/containers/network/clean`, {}); +}; export const deleteNetwork = (params: Container.BatchDelete) => { return http.post(`/containers/network/del`, params); }; diff --git a/frontend/src/views/container/network/index.vue b/frontend/src/views/container/network/index.vue index 1330311ff..1e9de35e3 100644 --- a/frontend/src/views/container/network/index.vue +++ b/frontend/src/views/container/network/index.vue @@ -97,8 +97,7 @@ import CreateDialog from '@/views/container/network/create/index.vue'; import DetailDrawer from '@/views/container/network/detail/index.vue'; import { reactive, ref } from 'vue'; import { dateFormat } from '@/utils/date'; -import { newUUID } from '@/utils/id'; -import { deleteNetwork, searchNetwork, inspect, containerPrune } from '@/api/modules/container'; +import { deleteNetwork, searchNetwork, inspect, cleanNetworks } from '@/api/modules/container'; import { Container } from '@/api/interface/container'; import TaskLog from '@/components/log/task/index.vue'; import i18n from '@/lang'; @@ -138,15 +137,10 @@ const onClean = () => { type: 'info', }).then(async () => { loading.value = true; - let params = { - taskID: newUUID(), - pruneType: 'network', - withTagAll: false, - }; - await containerPrune(params) - .then(() => { + await cleanNetworks() + .then((res) => { loading.value = false; - openTaskLog(params.taskID); + openTaskLog(res.data.taskID); }) .catch(() => { loading.value = false;