mirror of
https://github.com/cuigh/swirl
synced 2024-12-28 14:51:57 +00:00
Add prune function to image and container
This commit is contained in:
parent
70837391d1
commit
bb48beec82
@ -18,6 +18,7 @@ type ContainerHandler struct {
|
|||||||
Delete web.HandlerFunc `path:"/delete" method:"post" auth:"container.delete" desc:"delete container"`
|
Delete web.HandlerFunc `path:"/delete" method:"post" auth:"container.delete" desc:"delete container"`
|
||||||
FetchLogs web.HandlerFunc `path:"/fetch-logs" auth:"container.logs" desc:"fetch logs of container"`
|
FetchLogs web.HandlerFunc `path:"/fetch-logs" auth:"container.logs" desc:"fetch logs of container"`
|
||||||
Connect web.HandlerFunc `path:"/connect" auth:"container.execute" desc:"connect to a running container"`
|
Connect web.HandlerFunc `path:"/connect" auth:"container.execute" desc:"connect to a running container"`
|
||||||
|
Prune web.HandlerFunc `path:"/prune" method:"post" auth:"container.delete" desc:"delete unused containers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContainer creates an instance of ContainerHandler
|
// NewContainer creates an instance of ContainerHandler
|
||||||
@ -28,6 +29,7 @@ func NewContainer(b biz.ContainerBiz) *ContainerHandler {
|
|||||||
Delete: containerDelete(b),
|
Delete: containerDelete(b),
|
||||||
FetchLogs: containerFetchLogs(b),
|
FetchLogs: containerFetchLogs(b),
|
||||||
Connect: containerConnect(b),
|
Connect: containerConnect(b),
|
||||||
|
Prune: containerPrune(b),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,11 +80,12 @@ func containerDelete(b biz.ContainerBiz) web.HandlerFunc {
|
|||||||
type Args struct {
|
type Args struct {
|
||||||
Node string `json:"node"`
|
Node string `json:"node"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
return func(ctx web.Context) (err error) {
|
return func(ctx web.Context) (err error) {
|
||||||
args := &Args{}
|
args := &Args{}
|
||||||
if err = ctx.Bind(args); err == nil {
|
if err = ctx.Bind(args); err == nil {
|
||||||
err = b.Delete(args.Node, args.ID, ctx.User())
|
err = b.Delete(args.Node, args.ID, args.Name, ctx.User())
|
||||||
}
|
}
|
||||||
return ajax(ctx, err)
|
return ajax(ctx, err)
|
||||||
}
|
}
|
||||||
@ -214,3 +217,25 @@ func containerConnect(b biz.ContainerBiz) web.HandlerFunc {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func containerPrune(b biz.ContainerBiz) web.HandlerFunc {
|
||||||
|
type Args struct {
|
||||||
|
Node string `json:"node"`
|
||||||
|
}
|
||||||
|
return func(ctx web.Context) (err error) {
|
||||||
|
args := &Args{}
|
||||||
|
if err = ctx.Bind(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, size, err := b.Prune(args.Node, ctx.User())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return success(ctx, data.Map{
|
||||||
|
"count": count,
|
||||||
|
"size": size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
24
api/image.go
24
api/image.go
@ -11,6 +11,7 @@ type ImageHandler struct {
|
|||||||
Search web.HandlerFunc `path:"/search" auth:"image.view" desc:"search images"`
|
Search web.HandlerFunc `path:"/search" auth:"image.view" desc:"search images"`
|
||||||
Find web.HandlerFunc `path:"/find" auth:"image.view" desc:"find image by id"`
|
Find web.HandlerFunc `path:"/find" auth:"image.view" desc:"find image by id"`
|
||||||
Delete web.HandlerFunc `path:"/delete" method:"post" auth:"image.delete" desc:"delete image"`
|
Delete web.HandlerFunc `path:"/delete" method:"post" auth:"image.delete" desc:"delete image"`
|
||||||
|
Prune web.HandlerFunc `path:"/prune" method:"post" auth:"image.delete" desc:"delete unused images"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewImage creates an instance of ImageHandler
|
// NewImage creates an instance of ImageHandler
|
||||||
@ -19,6 +20,7 @@ func NewImage(b biz.ImageBiz) *ImageHandler {
|
|||||||
Search: imageSearch(b),
|
Search: imageSearch(b),
|
||||||
Find: imageFind(b),
|
Find: imageFind(b),
|
||||||
Delete: imageDelete(b),
|
Delete: imageDelete(b),
|
||||||
|
Prune: imagePrune(b),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,3 +79,25 @@ func imageDelete(b biz.ImageBiz) web.HandlerFunc {
|
|||||||
return ajax(ctx, err)
|
return ajax(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func imagePrune(b biz.ImageBiz) web.HandlerFunc {
|
||||||
|
type Args struct {
|
||||||
|
Node string `json:"node"`
|
||||||
|
}
|
||||||
|
return func(ctx web.Context) (err error) {
|
||||||
|
args := &Args{}
|
||||||
|
if err = ctx.Bind(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, size, err := b.Prune(args.Node, ctx.User())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return success(ctx, data.Map{
|
||||||
|
"count": count,
|
||||||
|
"size": size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -103,14 +103,14 @@ func volumePrune(b biz.VolumeBiz) web.HandlerFunc {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
deletedVolumes, reclaimedSpace, err := b.Prune(args.Node, ctx.User())
|
count, size, err := b.Prune(args.Node, ctx.User())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return success(ctx, data.Map{
|
return success(ctx, data.Map{
|
||||||
"deletedVolumes": deletedVolumes,
|
"count": count,
|
||||||
"reclaimedSpace": reclaimedSpace,
|
"size": size,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,19 +14,21 @@ import (
|
|||||||
type ContainerBiz interface {
|
type ContainerBiz interface {
|
||||||
Search(node, name, status string, pageIndex, pageSize int) ([]*Container, int, error)
|
Search(node, name, status string, pageIndex, pageSize int) ([]*Container, int, error)
|
||||||
Find(node, id string) (container *Container, raw string, err error)
|
Find(node, id string) (container *Container, raw string, err error)
|
||||||
Delete(node, id string, user web.User) (err error)
|
Delete(node, id, name string, user web.User) (err error)
|
||||||
FetchLogs(node, id string, lines int, timestamps bool) (stdout, stderr string, err error)
|
FetchLogs(node, id string, lines int, timestamps bool) (stdout, stderr string, err error)
|
||||||
ExecCreate(node, id string, cmd string) (resp types.IDResponse, err error)
|
ExecCreate(node, id string, cmd string) (resp types.IDResponse, err error)
|
||||||
ExecAttach(node, id string) (resp types.HijackedResponse, err error)
|
ExecAttach(node, id string) (resp types.HijackedResponse, err error)
|
||||||
ExecStart(node, id string) error
|
ExecStart(node, id string) error
|
||||||
|
Prune(node string, user web.User) (count int, size uint64, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContainer(d *docker.Docker) ContainerBiz {
|
func NewContainer(d *docker.Docker, eb EventBiz) ContainerBiz {
|
||||||
return &containerBiz{d: d}
|
return &containerBiz{d: d, eb: eb}
|
||||||
}
|
}
|
||||||
|
|
||||||
type containerBiz struct {
|
type containerBiz struct {
|
||||||
d *docker.Docker
|
d *docker.Docker
|
||||||
|
eb EventBiz
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *containerBiz) Find(node, id string) (c *Container, raw string, err error) {
|
func (b *containerBiz) Find(node, id string) (c *Container, raw string, err error) {
|
||||||
@ -58,11 +60,11 @@ func (b *containerBiz) Search(node, name, status string, pageIndex, pageSize int
|
|||||||
return containers, total, nil
|
return containers, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *containerBiz) Delete(node, id string, user web.User) (err error) {
|
func (b *containerBiz) Delete(node, id, name string, user web.User) (err error) {
|
||||||
err = b.d.ContainerRemove(context.TODO(), node, id)
|
err = b.d.ContainerRemove(context.TODO(), node, id)
|
||||||
//if err == nil {
|
if err == nil {
|
||||||
// Event.CreateContainer(model.EventActionDelete, id, user)
|
b.eb.CreateContainer(EventActionDelete, id, name, user)
|
||||||
//}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +88,15 @@ func (b *containerBiz) FetchLogs(node, id string, lines int, timestamps bool) (s
|
|||||||
return stdout.String(), stderr.String(), nil
|
return stdout.String(), stderr.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *containerBiz) Prune(node string, user web.User) (count int, size uint64, err error) {
|
||||||
|
var report types.ContainersPruneReport
|
||||||
|
if report, err = b.d.ContainerPrune(context.TODO(), node); err == nil {
|
||||||
|
count, size = len(report.ContainersDeleted), report.SpaceReclaimed
|
||||||
|
b.eb.CreateContainer(EventActionPrune, "", "", user)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
12
biz/event.go
12
biz/event.go
@ -21,6 +21,8 @@ const (
|
|||||||
EventTypeStack EventType = "Stack"
|
EventTypeStack EventType = "Stack"
|
||||||
EventTypeConfig EventType = "Config"
|
EventTypeConfig EventType = "Config"
|
||||||
EventTypeSecret EventType = "Secret"
|
EventTypeSecret EventType = "Secret"
|
||||||
|
EventTypeImage EventType = "Image"
|
||||||
|
EventTypeContainer EventType = "Container"
|
||||||
EventTypeVolume EventType = "Volume"
|
EventTypeVolume EventType = "Volume"
|
||||||
EventTypeUser EventType = "User"
|
EventTypeUser EventType = "User"
|
||||||
EventTypeRole EventType = "Role"
|
EventTypeRole EventType = "Role"
|
||||||
@ -54,6 +56,8 @@ type EventBiz interface {
|
|||||||
CreateConfig(action EventAction, id, name string, user web.User)
|
CreateConfig(action EventAction, id, name string, user web.User)
|
||||||
CreateSecret(action EventAction, id, name string, user web.User)
|
CreateSecret(action EventAction, id, name string, user web.User)
|
||||||
CreateStack(action EventAction, name string, user web.User)
|
CreateStack(action EventAction, name string, user web.User)
|
||||||
|
CreateImage(action EventAction, id string, user web.User)
|
||||||
|
CreateContainer(action EventAction, id, name string, user web.User)
|
||||||
CreateVolume(action EventAction, name string, user web.User)
|
CreateVolume(action EventAction, name string, user web.User)
|
||||||
CreateUser(action EventAction, id, name string, user web.User)
|
CreateUser(action EventAction, id, name string, user web.User)
|
||||||
CreateRole(action EventAction, id, name string, user web.User)
|
CreateRole(action EventAction, id, name string, user web.User)
|
||||||
@ -110,6 +114,14 @@ func (b *eventBiz) CreateNode(action EventAction, id, name string, user web.User
|
|||||||
b.create(EventTypeNode, action, id, name, user)
|
b.create(EventTypeNode, action, id, name, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *eventBiz) CreateImage(action EventAction, id string, user web.User) {
|
||||||
|
b.create(EventTypeImage, action, id, "", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *eventBiz) CreateContainer(action EventAction, id, name string, user web.User) {
|
||||||
|
b.create(EventTypeContainer, action, id, name, user)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *eventBiz) CreateVolume(action EventAction, name string, user web.User) {
|
func (b *eventBiz) CreateVolume(action EventAction, name string, user web.User) {
|
||||||
b.create(EventTypeVolume, action, name, name, user)
|
b.create(EventTypeVolume, action, name, name, user)
|
||||||
}
|
}
|
||||||
|
23
biz/image.go
23
biz/image.go
@ -15,14 +15,16 @@ type ImageBiz interface {
|
|||||||
Search(node, name string, pageIndex, pageSize int) ([]*Image, int, error)
|
Search(node, name string, pageIndex, pageSize int) ([]*Image, int, error)
|
||||||
Find(node, name string) (image *Image, raw string, err error)
|
Find(node, name string) (image *Image, raw string, err error)
|
||||||
Delete(node, id string, user web.User) (err error)
|
Delete(node, id string, user web.User) (err error)
|
||||||
|
Prune(node string, user web.User) (count int, size uint64, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImage(d *docker.Docker) ImageBiz {
|
func NewImage(d *docker.Docker, eb EventBiz) ImageBiz {
|
||||||
return &imageBiz{d: d}
|
return &imageBiz{d: d, eb: eb}
|
||||||
}
|
}
|
||||||
|
|
||||||
type imageBiz struct {
|
type imageBiz struct {
|
||||||
d *docker.Docker
|
d *docker.Docker
|
||||||
|
eb EventBiz
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *imageBiz) Find(node, id string) (img *Image, raw string, err error) {
|
func (b *imageBiz) Find(node, id string) (img *Image, raw string, err error) {
|
||||||
@ -62,9 +64,18 @@ func (b *imageBiz) Search(node, name string, pageIndex, pageSize int) (images []
|
|||||||
|
|
||||||
func (b *imageBiz) Delete(node, id string, user web.User) (err error) {
|
func (b *imageBiz) Delete(node, id string, user web.User) (err error) {
|
||||||
err = b.d.ImageRemove(context.TODO(), node, id)
|
err = b.d.ImageRemove(context.TODO(), node, id)
|
||||||
//if err == nil {
|
if err == nil {
|
||||||
// Event.CreateImage(model.EventActionDelete, id, user)
|
b.eb.CreateImage(EventActionDelete, id, user)
|
||||||
//}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *imageBiz) Prune(node string, user web.User) (count int, size uint64, err error) {
|
||||||
|
var report types.ImagesPruneReport
|
||||||
|
if report, err = b.d.ImagePrune(context.TODO(), node); err == nil {
|
||||||
|
count, size = len(report.ImagesDeleted), report.SpaceReclaimed
|
||||||
|
b.eb.CreateImage(EventActionPrune, "", user)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ type VolumeBiz interface {
|
|||||||
Find(node, name string) (volume *Volume, raw string, err error)
|
Find(node, name string) (volume *Volume, raw string, err error)
|
||||||
Delete(node, name string, user web.User) (err error)
|
Delete(node, name string, user web.User) (err error)
|
||||||
Create(volume *Volume, user web.User) (err error)
|
Create(volume *Volume, user web.User) (err error)
|
||||||
Prune(node string, user web.User) (deletedVolumes []string, reclaimedSpace uint64, err error)
|
Prune(node string, user web.User) (count int, size uint64, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVolume(d *docker.Docker, eb EventBiz) VolumeBiz {
|
func NewVolume(d *docker.Docker, eb EventBiz) VolumeBiz {
|
||||||
@ -85,11 +85,11 @@ func (b *volumeBiz) Create(vol *Volume, user web.User) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *volumeBiz) Prune(node string, user web.User) (deletedVolumes []string, reclaimedSpace uint64, err error) {
|
func (b *volumeBiz) Prune(node string, user web.User) (count int, size uint64, err error) {
|
||||||
var report types.VolumesPruneReport
|
var report types.VolumesPruneReport
|
||||||
report, err = b.d.VolumePrune(context.TODO(), node)
|
report, err = b.d.VolumePrune(context.TODO(), node)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
deletedVolumes, reclaimedSpace = report.VolumesDeleted, report.SpaceReclaimed
|
count, size = len(report.VolumesDeleted), report.SpaceReclaimed
|
||||||
b.eb.CreateVolume(EventActionPrune, "", user)
|
b.eb.CreateVolume(EventActionPrune, "", user)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -136,3 +136,12 @@ func (d *Docker) ContainerLogs(ctx context.Context, node, id string, lines int,
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainerPrune remove all unused containers.
|
||||||
|
func (d *Docker) ContainerPrune(ctx context.Context, node string) (report types.ContainersPruneReport, err error) {
|
||||||
|
var c *client.Client
|
||||||
|
if c, err = d.agent(node); err == nil {
|
||||||
|
report, err = c.ContainersPrune(ctx, filters.NewArgs())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -59,3 +59,12 @@ func (d *Docker) ImageRemove(ctx context.Context, node, id string) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImagePrune remove all unused images.
|
||||||
|
func (d *Docker) ImagePrune(ctx context.Context, node string) (report types.ImagesPruneReport, err error) {
|
||||||
|
var c *client.Client
|
||||||
|
if c, err = d.agent(node); err == nil {
|
||||||
|
report, err = c.ImagesPrune(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -79,6 +79,13 @@ export class ContainerApi {
|
|||||||
stderr: string;
|
stderr: string;
|
||||||
}>('/container/fetch-logs', args)
|
}>('/container/fetch-logs', args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prune(node: string) {
|
||||||
|
return ajax.post<{
|
||||||
|
count: number;
|
||||||
|
size: number;
|
||||||
|
}>('/container/prune', { node })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ContainerApi
|
export default new ContainerApi
|
||||||
|
@ -74,6 +74,13 @@ export class ImageApi {
|
|||||||
delete(node: string, id: string, name: string) {
|
delete(node: string, id: string, name: string) {
|
||||||
return ajax.post<Result<Object>>('/image/delete', { node, id, name })
|
return ajax.post<Result<Object>>('/image/delete', { node, id, name })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prune(node: string) {
|
||||||
|
return ajax.post<{
|
||||||
|
count: number;
|
||||||
|
size: number;
|
||||||
|
}>('/image/prune', { node })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ImageApi
|
export default new ImageApi
|
||||||
|
@ -36,11 +36,6 @@ export interface FindResult {
|
|||||||
raw: string;
|
raw: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PruneResult {
|
|
||||||
deletedVolumes: string[];
|
|
||||||
reclaimedSpace: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class VolumeApi {
|
export class VolumeApi {
|
||||||
find(node: string, name: string) {
|
find(node: string, name: string) {
|
||||||
return ajax.get<FindResult>('/volume/find', { node, name })
|
return ajax.get<FindResult>('/volume/find', { node, name })
|
||||||
@ -59,7 +54,10 @@ export class VolumeApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prune(node: string) {
|
prune(node: string) {
|
||||||
return ajax.post<PruneResult>('/volume/prune', { node })
|
return ajax.post<{
|
||||||
|
count: number;
|
||||||
|
size: number;
|
||||||
|
}>('/volume/prune', { node })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,6 +233,14 @@ export default {
|
|||||||
"export_chart": {
|
"export_chart": {
|
||||||
"title": "Export chart",
|
"title": "Export chart",
|
||||||
},
|
},
|
||||||
|
"prune_image": {
|
||||||
|
"title": "Prune image",
|
||||||
|
"body": "Are you sure you want to clean up unused images?",
|
||||||
|
},
|
||||||
|
"prune_container": {
|
||||||
|
"title": "Prune container",
|
||||||
|
"body": "Are you sure you want to clean up stopped containers?",
|
||||||
|
},
|
||||||
"prune_volume": {
|
"prune_volume": {
|
||||||
"title": "Prune volume",
|
"title": "Prune volume",
|
||||||
"body": "Are you sure you want to clean up unused data volumes?",
|
"body": "Are you sure you want to clean up unused data volumes?",
|
||||||
@ -384,6 +392,8 @@ export default {
|
|||||||
"service_notice": "This service belong to '%s' stack, usually you should modify original compose instead of updating it directly.",
|
"service_notice": "This service belong to '%s' stack, usually you should modify original compose instead of updating it directly.",
|
||||||
"password_notice": "You are a LDAP user, can not modify password here.",
|
"password_notice": "You are a LDAP user, can not modify password here.",
|
||||||
"setting_notice": "You must restart Swirl to activate modifications.",
|
"setting_notice": "You must restart Swirl to activate modifications.",
|
||||||
|
"prune_image_success": "A total of {count} images are cleaned up to free up {size} of space.",
|
||||||
|
"prune_container_success": "A total of {count} containers are cleaned up to free up {size} of space.",
|
||||||
"prune_volume_success": "A total of {count} data volumes are cleaned up to free up {size} of space.",
|
"prune_volume_success": "A total of {count} data volumes are cleaned up to free up {size} of space.",
|
||||||
"403": "Sorry, you don't have permission to access this page",
|
"403": "Sorry, you don't have permission to access this page",
|
||||||
"404": "The page you are requesting has been moved, deleted, renamed, or may not exist",
|
"404": "The page you are requesting has been moved, deleted, renamed, or may not exist",
|
||||||
|
@ -233,6 +233,14 @@ export default {
|
|||||||
"export_chart": {
|
"export_chart": {
|
||||||
"title": "导出图表",
|
"title": "导出图表",
|
||||||
},
|
},
|
||||||
|
"prune_image": {
|
||||||
|
"title": "清理镜像",
|
||||||
|
"body": "是否确实要清理未使用的镜像?",
|
||||||
|
},
|
||||||
|
"prune_container": {
|
||||||
|
"title": "清理容器",
|
||||||
|
"body": "是否确实要清理已停止的容器?",
|
||||||
|
},
|
||||||
"prune_volume": {
|
"prune_volume": {
|
||||||
"title": "清理数据卷",
|
"title": "清理数据卷",
|
||||||
"body": "是否确实要清理未使用的数据卷?",
|
"body": "是否确实要清理未使用的数据卷?",
|
||||||
@ -384,6 +392,8 @@ export default {
|
|||||||
"service_notice": "这个服务属于编排 {stack},通常你应该修改原始的编排而不是直接修改此服务信息,否则后续重新部署编排时这些修改将会丢失。",
|
"service_notice": "这个服务属于编排 {stack},通常你应该修改原始的编排而不是直接修改此服务信息,否则后续重新部署编排时这些修改将会丢失。",
|
||||||
"password_notice": "你是 LDAP 用户,不能在这里修改密码。",
|
"password_notice": "你是 LDAP 用户,不能在这里修改密码。",
|
||||||
"setting_notice": "你必须重启 Swirl 才能让设置生效。",
|
"setting_notice": "你必须重启 Swirl 才能让设置生效。",
|
||||||
|
"prune_image_success": "共清理 {count} 个镜像,释放 {size} 空间。",
|
||||||
|
"prune_container_success": "共清理 {count} 个容器,释放 {size} 空间。",
|
||||||
"prune_volume_success": "共清理 {count} 个数据卷,释放 {size} 空间。",
|
"prune_volume_success": "共清理 {count} 个数据卷,释放 {size} 空间。",
|
||||||
"403": "很抱歉,你没有权限访问此页面",
|
"403": "很抱歉,你没有权限访问此页面",
|
||||||
"404": "您要请求的页面已被移动、删除、重命名或可能不存在",
|
"404": "您要请求的页面已被移动、删除、重命名或可能不存在",
|
||||||
|
@ -1,5 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<x-page-header />
|
<x-page-header>
|
||||||
|
<template #action>
|
||||||
|
<n-button secondary size="small" type="warning" @click="prune">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<close-icon />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ t('buttons.prune') }}
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</x-page-header>
|
||||||
<n-space class="page-body" vertical :size="12">
|
<n-space class="page-body" vertical :size="12">
|
||||||
<n-space :size="12">
|
<n-space :size="12">
|
||||||
<n-select
|
<n-select
|
||||||
@ -38,13 +49,15 @@ import {
|
|||||||
NDataTable,
|
NDataTable,
|
||||||
NInput,
|
NInput,
|
||||||
NSelect,
|
NSelect,
|
||||||
|
NIcon,
|
||||||
} from "naive-ui";
|
} from "naive-ui";
|
||||||
|
import { CloseOutline as CloseIcon } from "@vicons/ionicons5";
|
||||||
import XPageHeader from "@/components/PageHeader.vue";
|
import XPageHeader from "@/components/PageHeader.vue";
|
||||||
import containerApi from "@/api/container";
|
import containerApi from "@/api/container";
|
||||||
import type { Container } from "@/api/container";
|
import type { Container } from "@/api/container";
|
||||||
import nodeApi from "@/api/node";
|
import nodeApi from "@/api/node";
|
||||||
import { useDataTable } from "@/utils/data-table";
|
import { useDataTable } from "@/utils/data-table";
|
||||||
import { renderButton, renderLink, renderTag } from "@/utils/render";
|
import { formatSize, renderButton, renderLink, renderTag } from "@/utils/render";
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@ -82,18 +95,35 @@ const columns = [
|
|||||||
title: t('fields.actions'),
|
title: t('fields.actions'),
|
||||||
key: "actions",
|
key: "actions",
|
||||||
render(i: Container, index: number) {
|
render(i: Container, index: number) {
|
||||||
return renderButton('error', t('buttons.delete'), () => deleteContainer(i, index), t('prompts.delete'))
|
return renderButton('error', t('buttons.delete'), () => remove(i, index), t('prompts.delete'))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const { state, pagination, fetchData, changePageSize } = useDataTable(containerApi.search, filter, false)
|
const { state, pagination, fetchData, changePageSize } = useDataTable(containerApi.search, filter, false)
|
||||||
|
|
||||||
async function deleteContainer(c: Container, index: number) {
|
async function remove(c: Container, index: number) {
|
||||||
const node = c.labels?.find(l => l.name === 'com.docker.swarm.node.id')
|
const node = c.labels?.find(l => l.name === 'com.docker.swarm.node.id')
|
||||||
await containerApi.delete(node?.value || '', c.id, '');
|
await containerApi.delete(node?.value || '', c.id, c.name);
|
||||||
state.data.splice(index, 1)
|
state.data.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function prune() {
|
||||||
|
window.dialog.warning({
|
||||||
|
title: t('dialogs.prune_container.title'),
|
||||||
|
content: t('dialogs.prune_container.body'),
|
||||||
|
positiveText: t('buttons.confirm'),
|
||||||
|
negativeText: t('buttons.cancel'),
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
const r = await containerApi.prune(filter.node);
|
||||||
|
window.message.info(t('texts.prune_container_success', {
|
||||||
|
count: r.data?.count,
|
||||||
|
size: formatSize(r.data?.size as number),
|
||||||
|
}));
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const r = await nodeApi.list(true)
|
const r = await nodeApi.list(true)
|
||||||
nodes.value = r.data?.map(n => ({ label: n.name, value: n.id }))
|
nodes.value = r.data?.map(n => ({ label: n.name, value: n.id }))
|
||||||
|
@ -1,5 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<x-page-header />
|
<x-page-header>
|
||||||
|
<template #action>
|
||||||
|
<n-button secondary size="small" type="warning" @click="prune">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<close-icon />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ t('buttons.prune') }}
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</x-page-header>
|
||||||
<n-space class="page-body" vertical :size="12">
|
<n-space class="page-body" vertical :size="12">
|
||||||
<n-space :size="12">
|
<n-space :size="12">
|
||||||
<n-select
|
<n-select
|
||||||
@ -38,7 +49,9 @@ import {
|
|||||||
NDataTable,
|
NDataTable,
|
||||||
NInput,
|
NInput,
|
||||||
NSelect,
|
NSelect,
|
||||||
|
NIcon,
|
||||||
} from "naive-ui";
|
} from "naive-ui";
|
||||||
|
import { CloseOutline as CloseIcon } from "@vicons/ionicons5";
|
||||||
import XPageHeader from "@/components/PageHeader.vue";
|
import XPageHeader from "@/components/PageHeader.vue";
|
||||||
import imageApi from "@/api/image";
|
import imageApi from "@/api/image";
|
||||||
import type { Image } from "@/api/image";
|
import type { Image } from "@/api/image";
|
||||||
@ -86,17 +99,34 @@ const columns = [
|
|||||||
title: t('fields.actions'),
|
title: t('fields.actions'),
|
||||||
key: "actions",
|
key: "actions",
|
||||||
render(i: Image, index: number) {
|
render(i: Image, index: number) {
|
||||||
return renderButton('error', t('buttons.delete'), () => deleteImage(i.id, index), t('prompts.delete'))
|
return renderButton('error', t('buttons.delete'), () => remove(i.id, index), t('prompts.delete'))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const { state, pagination, fetchData, changePageSize } = useDataTable(imageApi.search, filter, false)
|
const { state, pagination, fetchData, changePageSize } = useDataTable(imageApi.search, filter, false)
|
||||||
|
|
||||||
async function deleteImage(id: string, index: number) {
|
async function remove(id: string, index: number) {
|
||||||
await imageApi.delete(filter.node, id, "");
|
await imageApi.delete(filter.node, id, "");
|
||||||
state.data.splice(index, 1)
|
state.data.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function prune() {
|
||||||
|
window.dialog.warning({
|
||||||
|
title: t('dialogs.prune_image.title'),
|
||||||
|
content: t('dialogs.prune_image.body'),
|
||||||
|
positiveText: t('buttons.confirm'),
|
||||||
|
negativeText: t('buttons.cancel'),
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
const r = await imageApi.prune(filter.node);
|
||||||
|
window.message.info(t('texts.prune_image_success', {
|
||||||
|
count: r.data?.count,
|
||||||
|
size: formatSize(r.data?.size as number),
|
||||||
|
}));
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const r = await nodeApi.list(true)
|
const r = await nodeApi.list(true)
|
||||||
nodes.value = r.data?.map(n => ({ label: n.name, value: n.id }))
|
nodes.value = r.data?.map(n => ({ label: n.name, value: n.id }))
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<x-page-header>
|
<x-page-header>
|
||||||
<template #action>
|
<template #action>
|
||||||
<n-button secondary size="small" type="warning" @click="pruneVolume">
|
<n-button secondary size="small" type="warning" @click="prune">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon>
|
<n-icon>
|
||||||
<close-icon />
|
<close-icon />
|
||||||
@ -107,18 +107,18 @@ const columns = [
|
|||||||
title: t('fields.actions'),
|
title: t('fields.actions'),
|
||||||
key: "actions",
|
key: "actions",
|
||||||
render(v: Volume, index: number) {
|
render(v: Volume, index: number) {
|
||||||
return renderButton('error', t('buttons.delete'), () => deleteVolume(v.name, index), t('prompts.delete'))
|
return renderButton('error', t('buttons.delete'), () => remove(v.name, index), t('prompts.delete'))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const { state, pagination, fetchData, changePageSize } = useDataTable(volumeApi.search, filter, false)
|
const { state, pagination, fetchData, changePageSize } = useDataTable(volumeApi.search, filter, false)
|
||||||
|
|
||||||
async function deleteVolume(name: string, index: number) {
|
async function remove(name: string, index: number) {
|
||||||
await volumeApi.delete(filter.node, name);
|
await volumeApi.delete(filter.node, name);
|
||||||
state.data.splice(index, 1)
|
state.data.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pruneVolume() {
|
async function prune() {
|
||||||
window.dialog.warning({
|
window.dialog.warning({
|
||||||
title: t('dialogs.prune_volume.title'),
|
title: t('dialogs.prune_volume.title'),
|
||||||
content: t('dialogs.prune_volume.body'),
|
content: t('dialogs.prune_volume.body'),
|
||||||
@ -127,8 +127,8 @@ async function pruneVolume() {
|
|||||||
onPositiveClick: async () => {
|
onPositiveClick: async () => {
|
||||||
const r = await volumeApi.prune(filter.node);
|
const r = await volumeApi.prune(filter.node);
|
||||||
window.message.info(t('texts.prune_volume_success', {
|
window.message.info(t('texts.prune_volume_success', {
|
||||||
count: r.data?.deletedVolumes.length,
|
count: r.data?.count,
|
||||||
size: formatSize(r.data?.reclaimedSpace as number)
|
size: formatSize(r.data?.size as number)
|
||||||
}));
|
}));
|
||||||
fetchData();
|
fetchData();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user