mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-07 22:16:16 +08:00
fix: Optimize container cleanup mechanism (#9795)
Refs #7444 涉及到容器、镜像、网络、存储卷、构建缓存清理
This commit is contained in:
parent
ba4307c1dc
commit
9c292bebf0
25 changed files with 280 additions and 169 deletions
|
@ -315,7 +315,7 @@ func (b *BaseApi) ContainerUpgrade(c *gin.Context) {
|
|||
// @Summary Clean container
|
||||
// @Accept json
|
||||
// @Param request body dto.ContainerPrune true "request"
|
||||
// @Success 200 {object} dto.ContainerPruneReport
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/prune [post]
|
||||
|
@ -326,12 +326,11 @@ func (b *BaseApi) ContainerPrune(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
report, err := containerService.Prune(req)
|
||||
if err != nil {
|
||||
if err := containerService.Prune(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, report)
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
|
|
|
@ -138,7 +138,7 @@ func (b *BaseApi) ImagePush(c *gin.Context) {
|
|||
// @Summary Delete image
|
||||
// @Accept json
|
||||
// @Param request body dto.BatchDelete true "request"
|
||||
// @Success 200 {object} dto.ContainerPruneReport
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/image/remove [post]
|
||||
|
@ -149,13 +149,12 @@ func (b *BaseApi) ImageRemove(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
data, err := imageService.ImageRemove(req)
|
||||
if err != nil {
|
||||
if err := imageService.ImageRemove(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, data)
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Container Image
|
||||
|
|
|
@ -174,15 +174,11 @@ type ContainerCommit struct {
|
|||
}
|
||||
|
||||
type ContainerPrune struct {
|
||||
TaskID string `json:"taskID"`
|
||||
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network buildcache"`
|
||||
WithTagAll bool `json:"withTagAll"`
|
||||
}
|
||||
|
||||
type ContainerPruneReport struct {
|
||||
DeletedNumber int `json:"deletedNumber"`
|
||||
SpaceReclaimed int `json:"spaceReclaimed"`
|
||||
}
|
||||
|
||||
type Network struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
@ -227,8 +223,9 @@ type VolumeCreate struct {
|
|||
}
|
||||
|
||||
type BatchDelete struct {
|
||||
Force bool `json:"force"`
|
||||
Names []string `json:"names" validate:"required"`
|
||||
TaskID string `json:"taskID"`
|
||||
Force bool `json:"force"`
|
||||
Names []string `json:"names" validate:"required"`
|
||||
}
|
||||
|
||||
type ComposeInfo struct {
|
||||
|
|
|
@ -82,7 +82,7 @@ type IContainerService interface {
|
|||
CreateVolume(req dto.VolumeCreate) error
|
||||
TestCompose(req dto.ComposeCreate) (bool, error)
|
||||
ComposeUpdate(req dto.ComposeUpdate) error
|
||||
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
|
||||
Prune(req dto.ContainerPrune) error
|
||||
|
||||
LoadUsers(req dto.OperationWithName) []string
|
||||
|
||||
|
@ -417,66 +417,93 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
|||
return string(bytes), nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error) {
|
||||
report := dto.ContainerPruneReport{}
|
||||
func (u *ContainerService) Prune(req dto.ContainerPrune) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return report, err
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
pruneFilters := filters.NewArgs()
|
||||
if req.WithTagAll {
|
||||
pruneFilters.Add("dangling", "false")
|
||||
if req.PruneType != "image" {
|
||||
pruneFilters.Add("until", "24h")
|
||||
}
|
||||
}
|
||||
name := ""
|
||||
switch req.PruneType {
|
||||
case "container":
|
||||
rep, err := client.ContainersPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return report, err
|
||||
}
|
||||
report.DeletedNumber = len(rep.ContainersDeleted)
|
||||
report.SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
name = "Container"
|
||||
case "image":
|
||||
rep, err := client.ImagesPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return report, err
|
||||
}
|
||||
report.DeletedNumber = len(rep.ImagesDeleted)
|
||||
report.SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
case "network":
|
||||
rep, err := client.NetworksPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return report, err
|
||||
}
|
||||
report.DeletedNumber = len(rep.NetworksDeleted)
|
||||
name = "Image"
|
||||
case "volume":
|
||||
versions, err := client.ServerVersion(context.Background())
|
||||
if err != nil {
|
||||
return report, err
|
||||
}
|
||||
if common.ComparePanelVersion(versions.APIVersion, "1.42") {
|
||||
pruneFilters.Add("all", "true")
|
||||
}
|
||||
rep, err := client.VolumesPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return report, err
|
||||
}
|
||||
report.DeletedNumber = len(rep.VolumesDeleted)
|
||||
report.SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
name = "Volume"
|
||||
case "buildcache":
|
||||
opts := build.CachePruneOptions{}
|
||||
opts.All = true
|
||||
rep, err := client.BuildCachePrune(context.Background(), opts)
|
||||
if err != nil {
|
||||
return report, err
|
||||
}
|
||||
report.DeletedNumber = len(rep.CachesDeleted)
|
||||
report.SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
name = "BuildCache"
|
||||
case "network":
|
||||
name = "Network"
|
||||
}
|
||||
return report, nil
|
||||
taskItem, err := task.NewTaskWithOps(i18n.GetMsgByKey(name), task.TaskClean, task.TaskScopeContainer, req.TaskID, 1)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error {
|
||||
pruneFilters := filters.NewArgs()
|
||||
if req.WithTagAll {
|
||||
pruneFilters.Add("dangling", "false")
|
||||
if req.PruneType != "image" {
|
||||
pruneFilters.Add("until", "24h")
|
||||
}
|
||||
}
|
||||
DeletedNumber := 0
|
||||
SpaceReclaimed := 0
|
||||
switch req.PruneType {
|
||||
case "container":
|
||||
rep, err := client.ContainersPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
DeletedNumber = len(rep.ContainersDeleted)
|
||||
SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
case "image":
|
||||
rep, err := client.ImagesPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
DeletedNumber = len(rep.ImagesDeleted)
|
||||
SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
case "network":
|
||||
rep, err := client.NetworksPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
DeletedNumber = len(rep.NetworksDeleted)
|
||||
case "volume":
|
||||
versions, err := client.ServerVersion(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if common.ComparePanelVersion(versions.APIVersion, "1.42") {
|
||||
pruneFilters.Add("all", "true")
|
||||
}
|
||||
rep, err := client.VolumesPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
DeletedNumber = len(rep.VolumesDeleted)
|
||||
SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
case "buildcache":
|
||||
opts := build.CachePruneOptions{}
|
||||
opts.All = true
|
||||
rep, err := client.BuildCachePrune(context.Background(), opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
DeletedNumber = len(rep.CachesDeleted)
|
||||
SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||
}
|
||||
taskItem.Log(i18n.GetMsgWithMap("PruneHelper", map[string]interface{}{"name": i18n.GetMsgByKey(name), "count": DeletedNumber, "size": common.LoadSizeUnit2F(float64(SpaceReclaimed))}))
|
||||
return nil
|
||||
}, nil)
|
||||
go func() {
|
||||
_ = taskItem.Execute()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
|
||||
|
@ -1241,29 +1268,29 @@ func checkImageExist(client *client.Client, imageItem string) bool {
|
|||
}
|
||||
|
||||
func checkImageLike(client *client.Client, imageName string) bool {
|
||||
if client == nil {
|
||||
var err error
|
||||
client, err = docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
images, err := client.ImageList(context.Background(), image.ListOptions{})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if client == nil {
|
||||
var err error
|
||||
client, err = docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
images, err := client.ImageList(context.Background(), image.ListOptions{})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, img := range images {
|
||||
for _, tag := range img.RepoTags {
|
||||
parts := strings.Split(tag, "/")
|
||||
imageNameWithTag := parts[len(parts)-1]
|
||||
for _, img := range images {
|
||||
for _, tag := range img.RepoTags {
|
||||
parts := strings.Split(tag, "/")
|
||||
imageNameWithTag := parts[len(parts)-1]
|
||||
|
||||
if imageNameWithTag == imageName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
if imageNameWithTag == imageName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func pullImages(task *task.Task, client *client.Client, imageName string) error {
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
|
@ -41,7 +42,7 @@ type IImageService interface {
|
|||
ImageLoad(req dto.ImageLoad) error
|
||||
ImageSave(req dto.ImageSave) error
|
||||
ImagePush(req dto.ImagePush) error
|
||||
ImageRemove(req dto.BatchDelete) (dto.ContainerPruneReport, error)
|
||||
ImageRemove(req dto.BatchDelete) error
|
||||
ImageTag(req dto.ImageTag) error
|
||||
}
|
||||
|
||||
|
@ -386,34 +387,44 @@ func (u *ImageService) ImagePush(req dto.ImagePush) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *ImageService) ImageRemove(req dto.BatchDelete) (dto.ContainerPruneReport, error) {
|
||||
report := dto.ContainerPruneReport{}
|
||||
func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return report, err
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
for _, id := range req.Names {
|
||||
imageItem, _, err := client.ImageInspectWithRaw(context.TODO(), id)
|
||||
if err != nil {
|
||||
return report, err
|
||||
}
|
||||
if _, err := client.ImageRemove(context.TODO(), id, image.RemoveOptions{Force: req.Force, PruneChildren: true}); err != nil {
|
||||
if strings.Contains(err.Error(), "image is being used") || strings.Contains(err.Error(), "is using") {
|
||||
if strings.Contains(id, "sha256:") {
|
||||
return report, buserr.New("ErrObjectInUsed")
|
||||
}
|
||||
return report, buserr.WithDetail("ErrInUsed", id, nil)
|
||||
}
|
||||
if strings.Contains(err.Error(), "image has dependent") {
|
||||
return report, buserr.New("ErrObjectBeDependent")
|
||||
}
|
||||
return report, err
|
||||
}
|
||||
report.DeletedNumber++
|
||||
report.SpaceReclaimed += int(imageItem.Size)
|
||||
taskItem, err := task.NewTaskWithOps(task.TaskScopeImage, task.TaskDelete, task.TaskScopeContainer, req.TaskID, 1)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
||||
return err
|
||||
}
|
||||
return report, nil
|
||||
|
||||
for _, id := range req.Names {
|
||||
taskItem.AddSubTask(i18n.GetMsgByKey("TaskDelete")+id, func(t *task.Task) error {
|
||||
imageItem, err := client.ImageInspect(context.TODO(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := client.ImageRemove(context.TODO(), id, image.RemoveOptions{Force: req.Force, PruneChildren: true}); err != nil {
|
||||
if strings.Contains(err.Error(), "image is being used") || strings.Contains(err.Error(), "is using") {
|
||||
if strings.Contains(id, "sha256:") {
|
||||
return buserr.New("ErrObjectInUsed")
|
||||
}
|
||||
return buserr.WithDetail("ErrInUsed", id, nil)
|
||||
}
|
||||
if strings.Contains(err.Error(), "image has dependent") {
|
||||
return buserr.New("ErrObjectBeDependent")
|
||||
}
|
||||
return err
|
||||
}
|
||||
taskItem.Log(i18n.GetMsgWithMap("ImageRemoveHelper", map[string]interface{}{"name": id, "size": common.LoadSizeUnit2F(float64(imageItem.Size))}))
|
||||
return nil
|
||||
}, nil)
|
||||
}
|
||||
go func() {
|
||||
_ = taskItem.Execute()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatFileSize(fileSize int64) (size string) {
|
||||
|
|
|
@ -65,6 +65,7 @@ const (
|
|||
TaskPull = "TaskPull"
|
||||
TaskCommit = "TaskCommit"
|
||||
TaskPush = "TaskPush"
|
||||
TaskClean = "TaskClean"
|
||||
TaskHandle = "TaskHandle"
|
||||
)
|
||||
|
||||
|
|
|
@ -173,6 +173,11 @@ ErrObjectInUsed: 'The object is in use and cannot be deleted'
|
|||
ErrObjectBeDependent: 'This image depends on other images and cannot be deleted'
|
||||
ErrPortRules: 'Port number does not match, please re-enter!'
|
||||
ErrPgImagePull: 'Image pull timed out, please configure image acceleration or manually pull the {{ .name }} image and try again'
|
||||
PruneHelper: "This cleanup removed {{ .name }} {{ .count }} items, freeing {{ .size }} disk space"
|
||||
ImageRemoveHelper: "Deleted image {{ .name }}, freeing {{ .size }} disk space"
|
||||
BuildCache: "Build cache"
|
||||
Volume: "Storage volume"
|
||||
Network: "Network"
|
||||
|
||||
#runtime
|
||||
ErrFileNotExist: '{{ .detail }} file does not exist! Please check the integrity of the source file!'
|
||||
|
@ -290,6 +295,7 @@ TaskPull: 'Pull'
|
|||
TaskCommit: 'Commit'
|
||||
TaskBuild: 'Build'
|
||||
TaskPush: 'Push'
|
||||
TaskClean: "Cleanup"
|
||||
TaskHandle: 'Execute'
|
||||
Website: 'Website'
|
||||
App: 'Application'
|
||||
|
|
|
@ -173,6 +173,11 @@ ErrObjectInUsed: 'オブジェクトは使用中のため削除できません'
|
|||
ErrObjectBeDependent: 'このイメージは他のイメージに依存しているため、削除できません'
|
||||
ErrPortRules: 'ポート番号が一致しません。再入力してください。'
|
||||
ErrPgImagePull: 'イメージのプルがタイムアウトしました。イメージのアクセラレーションを設定するか、{{ .name }} イメージを手動でプルして再試行してください'
|
||||
PruneHelper: "今回のクリーンアップで{{ .name }} {{ .count }}個を削除し、{{ .size }}のディスク領域を解放しました"
|
||||
ImageRemoveHelper: "イメージ{{ .name }}を削除し、{{ .size }}のディスク領域を解放しました"
|
||||
BuildCache: "ビルドキャッシュ"
|
||||
Volume: "ストレージボリューム"
|
||||
Network: "ネットワーク"
|
||||
|
||||
#runtime
|
||||
ErrFileNotExist: '{{ .detail }} ファイルが存在しません。ソース ファイルの整合性を確認してください。'
|
||||
|
@ -290,6 +295,7 @@ TaskPull: 'プル'
|
|||
TaskCommit: 'コミット'
|
||||
TaskBuild: 'ビルド'
|
||||
TaskPush: 'プッシュ'
|
||||
TaskClean: "クリーンアップ"
|
||||
TaskHandle: '実行'
|
||||
Website: 'ウェブサイト'
|
||||
App: 'アプリケーション'
|
||||
|
|
|
@ -173,6 +173,11 @@ ErrObjectInUsed: '개체가 사용 중이므로 삭제할 수 없습니다'
|
|||
ErrObjectBeDependent: '이 이미지는 다른 이미지에 의존하므로 삭제할 수 없습니다'
|
||||
ErrPortRules: '포트 번호가 일치하지 않습니다. 다시 입력하세요!'
|
||||
ErrPgImagePull: '이미지 풀링 시간이 초과되었습니다. 이미지 가속을 구성하거나 {{ .name }} 이미지를 수동으로 풀링한 다음 다시 시도하세요.'
|
||||
PruneHelper: "이번 정리에서 {{ .name }} {{ .count }}개를 제거하여 {{ .size }} 디스크 공간을 확보했습니다"
|
||||
ImageRemoveHelper: "이미지 {{ .name }} 삭제, {{ .size }} 디스크 공간 확보"
|
||||
BuildCache: "빌드 캐시"
|
||||
Volume: "스토리지 볼륨"
|
||||
Network: "네트워크"
|
||||
|
||||
#실행 시간
|
||||
ErrFileNotExist: '{{ .detail }} 파일이 존재하지 않습니다! 소스 파일의 무결성을 확인하세요!'
|
||||
|
@ -290,6 +295,7 @@ TaskPull: '당기기'
|
|||
TaskCommit: '커밋'
|
||||
TaskBuild: '빌드'
|
||||
TaskPush: '푸시'
|
||||
TaskClean: "정리"
|
||||
TaskHandle: '실행'
|
||||
Website: '웹사이트'
|
||||
App: '애플리케이션'
|
||||
|
|
|
@ -172,6 +172,11 @@ ErrObjectInUsed: 'Objek sedang digunakan dan tidak boleh dipadamkan'
|
|||
ErrObjectBeDependent: 'Imej ini bergantung pada imej lain dan tidak boleh dipadamkan'
|
||||
ErrPortRules: 'Nombor port tidak sepadan, sila masukkan semula!'
|
||||
ErrPgImagePull: 'Tarikh imej tamat masa, sila konfigurasikan pecutan imej atau tarik imej {{ .name }} secara manual dan cuba lagi'
|
||||
PruneHelper: "Pembersihan ini membuang {{ .name }} {{ .count }} item, membebaskan {{ .size }} ruang cakera"
|
||||
ImageRemoveHelper: "Padam imej {{ .name }}, membebaskan {{ .size }} ruang cakera"
|
||||
BuildCache: "Cache binaan"
|
||||
Volume: "Jilid storan"
|
||||
Network: "Rangkaian"
|
||||
|
||||
#masa berjalan
|
||||
ErrFileNotExist: 'Fail {{ .detail }} tidak wujud! Sila semak integriti fail sumber!'
|
||||
|
@ -289,6 +294,7 @@ TaskPull: 'Tarik'
|
|||
TaskCommit: 'Komit'
|
||||
TaskBuild: 'Bina'
|
||||
TaskPush: 'Tolak'
|
||||
TaskClean: "Pembersihan"
|
||||
TaskHandle: 'Laksanakan'
|
||||
Website: 'Laman web'
|
||||
App: 'Aplikasi'
|
||||
|
|
|
@ -173,6 +173,11 @@ ErrObjectInUsed: 'O objeto está em uso e não pode ser excluído'
|
|||
ErrObjectBeDependent: 'Esta imagem depende de outras imagens e não pode ser excluída'
|
||||
ErrPortRules: 'O número da porta não corresponde, digite novamente!'
|
||||
ErrPgImagePull: 'Tempo limite para extração de imagem. Configure a aceleração de imagem ou extraia manualmente a imagem {{ .name }} e tente novamente'
|
||||
PruneHelper: "Esta limpeza removeu {{ .name }} {{ .count }} itens, liberando {{ .size }} de espaço em disco"
|
||||
ImageRemoveHelper: "Excluída a imagem {{ .name }}, liberando {{ .size }} de espaço em disco"
|
||||
BuildCache: "Cache de construção"
|
||||
Volume: "Volume de armazenamento"
|
||||
Network: "Rede"
|
||||
|
||||
#tempo de execução
|
||||
ErrFileNotExist: 'O arquivo {{ .detail }} não existe! Verifique a integridade do arquivo de origem!'
|
||||
|
@ -290,6 +295,7 @@ TaskPull: 'Puxar'
|
|||
TaskCommit: 'Commit'
|
||||
TaskBuild: 'Construir'
|
||||
TaskPush: 'Empurrar'
|
||||
TaskClean: "Limpeza"
|
||||
TaskHandle: 'Executar'
|
||||
Website: 'Site'
|
||||
App: 'Aplicativo'
|
||||
|
|
|
@ -173,6 +173,11 @@ ErrObjectInUsed: 'Объект используется и не может бы
|
|||
ErrObjectBeDependent: 'Это изображение зависит от других изображений и не может быть удалено'
|
||||
ErrPortRules: 'Номер порта не совпадает, введите заново!'
|
||||
ErrPgImagePull: 'Время извлечения изображения истекло. Настройте ускорение изображения или вручную извлеките изображение {{ .name }} и повторите попытку'
|
||||
PruneHelper: "Очистка удалила {{ .name }} в количестве {{ .count }}, освободив {{ .size }} дискового пространства"
|
||||
ImageRemoveHelper: "Удалён образ {{ .name }}, освобождено {{ .size }} дискового пространства"
|
||||
BuildCache: "Кэш сборки"
|
||||
Volume: "Том хранилища"
|
||||
Network: "Сеть"
|
||||
|
||||
#время выполнения
|
||||
ErrFileNotExist: 'Файл {{ .detail }} не существует! Проверьте целостность исходного файла!'
|
||||
|
@ -290,6 +295,7 @@ TaskPull: 'Вытянуть'
|
|||
TaskCommit: 'Kоммит'
|
||||
ЗадачаСборка: 'Сборка'
|
||||
TaskPush: 'Push'
|
||||
TaskClean: "Очистка"
|
||||
TaskHandle: 'Выполнить'
|
||||
Website: 'Be6-сайт'
|
||||
App: 'Приложение'
|
||||
|
|
|
@ -173,6 +173,11 @@ ErrObjectInUsed: 'Nesne kullanımda ve silinemez'
|
|||
ErrObjectBeDependent: 'Bu image diğer imagelere bağlı ve silinemez'
|
||||
ErrPortRules: 'Port numarası eşleşmiyor, lütfen yeniden girin!'
|
||||
ErrPgImagePull: 'Image çekme zaman aşımı, lütfen image hızlandırma yapılandırın veya manuel olarak {{ .name }} imageını çekin ve tekrar deneyin'
|
||||
PruneHelper: "Bu temizleme {{ .name }} {{ .count }} öğe kaldırdı, {{ .size }} disk alanı boşalttı"
|
||||
ImageRemoveHelper: "{{ .name }} imajı silindi, {{ .size }} disk alanı boşalttı"
|
||||
BuildCache: "Derleme önbelleği"
|
||||
Volume: "Depolama hacmi"
|
||||
Network: "Ağ"
|
||||
|
||||
#runtime
|
||||
ErrFileNotExist: '{{ .detail }} dosyası mevcut değil! Lütfen kaynak dosyanın bütünlüğünü kontrol edin!'
|
||||
|
@ -288,6 +293,7 @@ TaskPull: 'Çek'
|
|||
TaskCommit: 'işleme'
|
||||
TaskBuild: 'Yapı'
|
||||
TaskPush: 'Gönder'
|
||||
TaskClean: "Temizleme"
|
||||
TaskHandle: 'Yürüt'
|
||||
Website: 'Web Sitesi'
|
||||
App: 'Uygulama'
|
||||
|
|
|
@ -172,6 +172,11 @@ ErrObjectInUsed: '該物件正被使用,無法刪除'
|
|||
ErrObjectBeDependent: '此鏡像依賴其他鏡像,無法刪除'
|
||||
ErrPortRules: '連接埠數目不匹配,請重新輸入!'
|
||||
ErrPgImagePull: '鏡像拉取逾時,請配置鏡像加速或手動拉取{{ .name }} 鏡像後重試'
|
||||
PruneHelper: "本次清理 {{ .name }} {{ .count }} 個,釋放磁盤空間 {{ .size }}"
|
||||
ImageRemoveHelper: "刪除鏡像 {{ .name }} ,釋放磁盤空間 {{ .size }}"
|
||||
BuildCache: "構建緩存"
|
||||
Volume: "存儲卷"
|
||||
Network: "網絡"
|
||||
|
||||
#runtime
|
||||
ErrFileNotExist: '{{ .detail }} 檔案不存在!請檢查來源檔案完整性!'
|
||||
|
@ -289,6 +294,7 @@ TaskPull: '拉取'
|
|||
TaskCommit: '制作'
|
||||
TaskBuild: '建置'
|
||||
TaskPush: '推送'
|
||||
TaskClean: "清理"
|
||||
TaskHandle: '執行'
|
||||
Website: '網站'
|
||||
App: '應用程式'
|
||||
|
|
|
@ -172,6 +172,11 @@ ErrObjectInUsed: "该对象正被使用,无法删除"
|
|||
ErrObjectBeDependent: "该镜像依赖于其他镜像,无法删除"
|
||||
ErrPortRules: "端口数目不匹配,请重新输入!"
|
||||
ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 {{ .name }} 镜像后重试"
|
||||
PruneHelper: "本次清理 {{ .name }} {{ .count }} 个,释放磁盘空间 {{ .size }}"
|
||||
ImageRemoveHelper: "删除镜像 {{ .name }} ,释放磁盘空间 {{ .size }}"
|
||||
BuildCache: "构建缓存"
|
||||
Volume: "存储卷"
|
||||
Network: "网络"
|
||||
|
||||
#runtime
|
||||
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"
|
||||
|
@ -289,6 +294,7 @@ TaskPull: "拉取"
|
|||
TaskCommit: "制作"
|
||||
TaskBuild: "构建"
|
||||
TaskPush: "推送"
|
||||
TaskClean: "清理"
|
||||
TaskHandle: "执行"
|
||||
Website: "网站"
|
||||
App: "应用"
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { ReqPage } from '.';
|
||||
|
||||
export namespace Cronjob {
|
||||
export interface Search extends ReqPage {
|
||||
info: string;
|
||||
groupIDs: Array<number>;
|
||||
orderBy?: string;
|
||||
order?: string;
|
||||
}
|
||||
export interface CronjobInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
|
|
|
@ -20,7 +20,6 @@ export interface ReqPage {
|
|||
}
|
||||
export interface SearchWithPage {
|
||||
info: string;
|
||||
groupIDs: Array<number>;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
orderBy?: string;
|
||||
|
|
|
@ -53,7 +53,7 @@ export const containerOperator = (params: Container.ContainerOperate) => {
|
|||
return http.post(`/containers/operate`, params);
|
||||
};
|
||||
export const containerPrune = (params: Container.ContainerPrune) => {
|
||||
return http.post<Container.ContainerPruneReport>(`/containers/prune`, params);
|
||||
return http.post(`/containers/prune`, params);
|
||||
};
|
||||
export const inspect = (params: Container.ContainerInspect) => {
|
||||
return http.post<string>(`/containers/inspect`, params);
|
||||
|
@ -95,7 +95,7 @@ export const imageTag = (params: Container.ImageTag) => {
|
|||
return http.post(`/containers/image/tag`, params);
|
||||
};
|
||||
export const imageRemove = (params: Container.BatchDelete) => {
|
||||
return http.post<Container.ContainerPruneReport>(`/containers/image/remove`, params);
|
||||
return http.post(`/containers/image/remove`, params);
|
||||
};
|
||||
|
||||
// network
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ResPage, SearchWithPage } from '../interface';
|
|||
import { Cronjob } from '../interface/cronjob';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
|
||||
export const searchCronjobPage = (params: SearchWithPage) => {
|
||||
export const searchCronjobPage = (params: Cronjob.Search) => {
|
||||
return http.post<ResPage<Cronjob.CronjobInfo>>(`/cronjobs/search`, params);
|
||||
};
|
||||
|
||||
|
|
|
@ -18,42 +18,45 @@
|
|||
</span>
|
||||
</template>
|
||||
</DialogPro>
|
||||
<TaskLog ref="taskLogRef" width="70%" @close="onSearch" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { containerPrune } from '@/api/modules/container';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import { ref } from 'vue';
|
||||
import { computeSize } from '@/utils/util';
|
||||
import { newUUID } from '@/utils/util';
|
||||
|
||||
const loading = ref(false);
|
||||
const open = ref<boolean>(false);
|
||||
const taskLogRef = ref();
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const onClean = async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
taskID: newUUID(),
|
||||
pruneType: 'container',
|
||||
withTagAll: false,
|
||||
};
|
||||
await containerPrune(params)
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(
|
||||
i18n.global.t('container.cleanSuccessWithSpace', [
|
||||
res.data.deletedNumber,
|
||||
computeSize(res.data.spaceReclaimed),
|
||||
]),
|
||||
);
|
||||
open.value = false;
|
||||
emit('search');
|
||||
openTaskLog(params.taskID);
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
const openTaskLog = (taskID: string) => {
|
||||
taskLogRef.value.openWithTaskID(taskID);
|
||||
};
|
||||
|
||||
const onSearch = () => {
|
||||
emit('search');
|
||||
};
|
||||
|
||||
const acceptParams = (): void => {
|
||||
open.value = true;
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" :heightDiff="300">
|
||||
<el-table-column label="ID" prop="id" width="140" show-overflow-tooltip>
|
||||
<el-table-column label="ID" prop="id" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-text type="primary" class="cursor-pointer" @click="onInspect(row.id)">
|
||||
{{ row.id.replaceAll('sha256:', '').substring(0, 12) }}
|
||||
|
@ -92,12 +92,13 @@
|
|||
<Build ref="dialogBuildRef" @search="search" />
|
||||
<Delete ref="dialogDeleteRef" @search="search" />
|
||||
<Prune ref="dialogPruneRef" @search="search" />
|
||||
<TaskLog ref="taskLogRef" width="70%" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { dateFormat, newUUID } from '@/utils/util';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import Pull from '@/views/container/image/pull/index.vue';
|
||||
import Tag from '@/views/container/image/tag/index.vue';
|
||||
|
@ -109,13 +110,14 @@ import Delete from '@/views/container/image/delete/index.vue';
|
|||
import Prune from '@/views/container/image/prune/index.vue';
|
||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import { searchImage, listImageRepo, imageRemove, inspect, containerPrune } from '@/api/modules/container';
|
||||
import i18n from '@/lang';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const taskLogRef = ref();
|
||||
const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
|
@ -220,13 +222,14 @@ const onOpenBuildCache = () => {
|
|||
}).then(async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
taskID: newUUID(),
|
||||
pruneType: 'buildcache',
|
||||
withTagAll: false,
|
||||
};
|
||||
await containerPrune(params)
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('container.cleanSuccess', [res.data.deletedNumber]));
|
||||
openTaskLog(params.taskID);
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
|
@ -234,6 +237,9 @@ const onOpenBuildCache = () => {
|
|||
});
|
||||
});
|
||||
};
|
||||
const openTaskLog = (taskID: string) => {
|
||||
taskLogRef.value.openWithTaskID(taskID);
|
||||
};
|
||||
|
||||
const onOpenload = () => {
|
||||
dialogLoadRef.value!.acceptParams();
|
||||
|
|
|
@ -38,13 +38,14 @@
|
|||
</span>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
<TaskLog ref="taskLogRef" width="70%" @close="onSearch" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { containerPrune, imageRemove, listAllImage } from '@/api/modules/container';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { computeSize } from '@/utils/util';
|
||||
import { newUUID } from '@/utils/util';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const dialogVisible = ref(false);
|
||||
|
@ -54,6 +55,7 @@ const loading = ref();
|
|||
const unTagList = ref([]);
|
||||
const unUsedList = ref([]);
|
||||
const data = ref([]);
|
||||
const taskLogRef = ref();
|
||||
|
||||
const checkAll = ref(false);
|
||||
const isIndeterminate = ref(false);
|
||||
|
@ -120,6 +122,10 @@ const handleClose = () => {
|
|||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const onSearch = () => {
|
||||
emit('search');
|
||||
};
|
||||
|
||||
const onClean = async () => {
|
||||
loading.value = true;
|
||||
if (checkAll.value) {
|
||||
|
@ -131,41 +137,34 @@ const onClean = async () => {
|
|||
|
||||
const prune = async () => {
|
||||
let params = {
|
||||
taskID: newUUID(),
|
||||
pruneType: 'image',
|
||||
withTagAll: scope.value === 'unused',
|
||||
};
|
||||
await containerPrune(params)
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
dialogVisible.value = false;
|
||||
MsgSuccess(
|
||||
i18n.global.t('container.cleanSuccessWithSpace', [
|
||||
res.data.deletedNumber,
|
||||
computeSize(res.data.spaceReclaimed),
|
||||
]),
|
||||
);
|
||||
emit('search');
|
||||
openTaskLog(params.taskID);
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
const openTaskLog = (taskID: string) => {
|
||||
taskLogRef.value.openWithTaskID(taskID);
|
||||
};
|
||||
|
||||
const removeImage = async () => {
|
||||
let params = {
|
||||
taskID: newUUID(),
|
||||
names: checkedLists.value,
|
||||
};
|
||||
await imageRemove(params)
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
dialogVisible.value = false;
|
||||
MsgSuccess(
|
||||
i18n.global.t('container.cleanSuccessWithSpace', [
|
||||
res.data.deletedNumber,
|
||||
computeSize(res.data.spaceReclaimed),
|
||||
]),
|
||||
);
|
||||
emit('search');
|
||||
openTaskLog(params.taskID);
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
<OpDialog ref="opRef" @search="search" />
|
||||
<CodemirrorDrawer ref="myDetail" />
|
||||
<CreateDialog @search="search" ref="dialogCreateRef" />
|
||||
<TaskLog ref="taskLogRef" width="70%" @close="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -95,16 +96,17 @@
|
|||
import CreateDialog from '@/views/container/network/create/index.vue';
|
||||
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { dateFormat, newUUID } from '@/utils/util';
|
||||
import { deleteNetwork, searchNetwork, inspect, containerPrune } from '@/api/modules/container';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||
|
||||
const loading = ref();
|
||||
const myDetail = ref();
|
||||
const taskLogRef = ref();
|
||||
|
||||
const data = ref();
|
||||
const selects = ref<any>([]);
|
||||
|
@ -136,20 +138,23 @@ const onClean = () => {
|
|||
}).then(async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
taskID: newUUID(),
|
||||
pruneType: 'network',
|
||||
withTagAll: false,
|
||||
};
|
||||
await containerPrune(params)
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('container.cleanSuccess', [res.data.deletedNumber]));
|
||||
search();
|
||||
openTaskLog(params.taskID);
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
const openTaskLog = (taskID: string) => {
|
||||
taskLogRef.value.openWithTaskID(taskID);
|
||||
};
|
||||
|
||||
function selectable(row) {
|
||||
return !row.isSystem;
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
|
||||
<CodemirrorDrawer ref="myDetail" />
|
||||
<CreateDialog @search="search" ref="dialogCreateRef" />
|
||||
<TaskLog ref="taskLogRef" width="70%" @close="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -91,16 +92,17 @@ import CreateDialog from '@/views/container/volume/create/index.vue';
|
|||
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
|
||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
import { computeSize, dateFormat } from '@/utils/util';
|
||||
import { dateFormat, newUUID } from '@/utils/util';
|
||||
import { deleteVolume, searchVolume, inspect, containerPrune } from '@/api/modules/container';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import router from '@/routers';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const taskLogRef = ref();
|
||||
const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
|
@ -174,25 +176,23 @@ const onClean = () => {
|
|||
}).then(async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
taskID: newUUID(),
|
||||
pruneType: 'volume',
|
||||
withTagAll: false,
|
||||
};
|
||||
await containerPrune(params)
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(
|
||||
i18n.global.t('container.cleanSuccessWithSpace', [
|
||||
res.data.deletedNumber,
|
||||
computeSize(res.data.spaceReclaimed),
|
||||
]),
|
||||
);
|
||||
search();
|
||||
openTaskLog(params.taskID);
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
const openTaskLog = (taskID: string) => {
|
||||
taskLogRef.value.openWithTaskID(taskID);
|
||||
};
|
||||
|
||||
const batchDelete = async (row: Container.VolumeInfo | null) => {
|
||||
let names = [];
|
||||
|
|
|
@ -125,6 +125,7 @@
|
|||
<Config ref="configRef" />
|
||||
<Supervisor ref="supervisorRef" />
|
||||
<Terminal ref="terminalRef" />
|
||||
<TaskLog ref="taskLogRef" width="70%" @close="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -132,10 +133,10 @@
|
|||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { DeleteRuntime, RuntimeDeleteCheck, SearchRuntimes } from '@/api/modules/runtime';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { dateFormat, newUUID } from '@/utils/util';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { containerPrune } from '@/api/modules/container';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import ExtManagement from './extension-management/index.vue';
|
||||
import Extensions from './extension-template/index.vue';
|
||||
|
@ -158,6 +159,7 @@ const mobile = computed(() => {
|
|||
return globalStore.isMobile();
|
||||
});
|
||||
|
||||
const taskLogRef = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'runtime-page-size',
|
||||
currentPage: 1,
|
||||
|
@ -354,20 +356,23 @@ const onOpenBuildCache = () => {
|
|||
}).then(async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
taskID: newUUID(),
|
||||
pruneType: 'buildcache',
|
||||
withTagAll: false,
|
||||
};
|
||||
await containerPrune(params)
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('container.cleanSuccess', [res.data.deletedNumber]));
|
||||
search();
|
||||
openTaskLog(params.taskID);
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
const openTaskLog = (taskID: string) => {
|
||||
taskLogRef.value.openWithTaskID(taskID);
|
||||
};
|
||||
|
||||
const toFolder = (folder: string) => {
|
||||
router.push({ path: '/hosts/files', query: { path: folder } });
|
||||
|
|
Loading…
Add table
Reference in a new issue