Skip to content

Commit 8040aae

Browse files
author
Timo
committed
Modify the mirroring function
1 parent 6fa5442 commit 8040aae

5 files changed

Lines changed: 169 additions & 31 deletions

File tree

pkg/app/app_cli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ func (app *App) handleCLIInput(choice string, services []*commands.Service, allS
368368
case "17":
369369
manager.RunVolumeMenu(app.DockerCommand, app.ReadInput)
370370
case "18":
371-
manager.RunImageMenu(app.DockerCommand, app.ReadInput, app.runInteractiveSubprocess)
371+
manager.RunImageMenu(app.DockerCommand, app.ReadInput, app.runInteractiveSubprocess, app.runFzfSelect, app.parseFzfResult)
372372
case "100":
373373
app.doServiceAction(app.Tr.Fix, services, true, false, func(s *commands.Service) error {
374374
if !s.IsLocal {

pkg/commands/image.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package commands
22

33
import (
44
"context"
5+
"os/exec"
56
"strings"
67

78
"github.com/docker/docker/api/types/filters"
@@ -151,3 +152,23 @@ func (i *Image) GetDisplaySize() string {
151152
func (i *Image) GetDisplayContentSize() string {
152153
return utils.FormatBinaryBytes(int(i.Image.VirtualSize))
153154
}
155+
156+
// Run runs the image with a specific name
157+
func (i *Image) Run(name string) error {
158+
command := "docker run -d"
159+
if name != "" {
160+
command += " --name " + i.OSCommand.Quote(name)
161+
}
162+
command += " " + i.ID
163+
return i.OSCommand.RunCommand(command)
164+
}
165+
166+
// GetInteractiveRunCmd returns a command to run the image interactively
167+
func (i *Image) GetInteractiveRunCmd(name string, shell string) *exec.Cmd {
168+
args := []string{"run", "-it", "--rm"}
169+
if name != "" {
170+
args = append(args, "--name", name)
171+
}
172+
args = append(args, i.ID, shell)
173+
return i.OSCommand.NewCmd("docker", args...)
174+
}

pkg/i18n/chinese.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,5 +214,22 @@ func chineseSet() TranslationSet {
214214
MenuVolumeManagement: "卷管理",
215215
MenuImageManagement: "镜像管理",
216216
MenuRepairService: "修复服务 (所有/指定) - 重新构建镜像",
217+
218+
// 镜像管理相关
219+
SearchImageTitle: "搜索所有镜像 (支持名称搜索, Esc 返回)",
220+
RunImage: "运行镜像",
221+
InputContainerName: "输入容器名称 (可选,直接回车使用默认): ",
222+
RunImageSuccess: "容器 %s 已启动",
223+
RunImageFailed: "运行镜像失败: %v",
224+
SelectActionForImage: "选择对镜像 [%s] 执行的操作 (Esc 返回)",
225+
ConfirmDeleteImage: "确定要删除镜像 %s (ID: %s) 吗? (y/n): ",
226+
DeletingImage: "正在删除镜像...",
227+
InvalidIndex: "无效的索引",
228+
DangerDeleteAllImages: "危险: 这将删除所有未被使用的镜像! 是否继续? (y/n): ",
229+
CleaningAllUnusedImages: "正在清理所有未使用镜像...",
230+
PullImage: "拉取镜像",
231+
DeleteSpecifiedImage: "删除指定镜像",
232+
DeleteAllImages: "删除所有镜像",
233+
DetectingShell: "正在检测可用 Shell...",
217234
}
218235
}

pkg/i18n/english.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,22 @@ type TranslationSet struct {
218218
MenuVolumeManagement string
219219
MenuImageManagement string
220220
MenuRepairService string
221+
// Image Management
222+
SearchImageTitle string
223+
RunImage string
224+
InputContainerName string
225+
RunImageSuccess string
226+
RunImageFailed string
227+
SelectActionForImage string
228+
ConfirmDeleteImage string
229+
DeletingImage string
230+
InvalidIndex string
231+
DangerDeleteAllImages string
232+
CleaningAllUnusedImages string
233+
PullImage string
234+
DeleteSpecifiedImage string
235+
DeleteAllImages string
236+
DetectingShell string
221237
}
222238

223239
func englishSet() TranslationSet {
@@ -443,5 +459,22 @@ func englishSet() TranslationSet {
443459
MenuVolumeManagement: "Volume Management",
444460
MenuImageManagement: "Image Management",
445461
MenuRepairService: "Repair Service (All/Specified) - Rebuild Image",
462+
463+
// Image Management
464+
SearchImageTitle: "Search all images (Supports name search, Esc to return)",
465+
RunImage: "Run Image",
466+
InputContainerName: "Input container name (optional, press Enter for default): ",
467+
RunImageSuccess: "Container %s is running",
468+
RunImageFailed: "Run image failed: %v",
469+
SelectActionForImage: "Select action for image [%s] (Esc to return)",
470+
ConfirmDeleteImage: "Are you sure you want to delete image %s (ID: %s)? (y/n): ",
471+
DeletingImage: "Deleting image...",
472+
InvalidIndex: "Invalid index",
473+
DangerDeleteAllImages: "DANGER: This will delete all unused images! Continue? (y/n): ",
474+
CleaningAllUnusedImages: "Cleaning all unused images...",
475+
PullImage: "Pull Image",
476+
DeleteSpecifiedImage: "Delete Specified Image",
477+
DeleteAllImages: "Delete All Images",
478+
DetectingShell: "Detecting available shell...",
446479
}
447480
}

pkg/manager/image.go

Lines changed: 97 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,32 @@ import (
88

99
"github.com/docker/docker/api/types/image"
1010
"github.com/yaogh99123/dcli/pkg/commands"
11+
"github.com/yaogh99123/dcli/pkg/i18n"
1112
"github.com/yaogh99123/dcli/pkg/utils"
1213
)
1314

1415
// RunImageMenu handles image management CLI interactions
15-
func RunImageMenu(dockerCmd *commands.DockerCommand, readInput func(string) string, runInteractiveSubprocess func(*exec.Cmd) error) {
16+
func RunImageMenu(
17+
dockerCmd *commands.DockerCommand,
18+
readInput func(string) string,
19+
runInteractiveSubprocess func(*exec.Cmd) error,
20+
runFzfSelect func(string, []string) string,
21+
parseFzfResult func(string) string,
22+
) {
23+
tr := dockerCmd.Tr
1624
for {
1725
images, err := dockerCmd.RefreshImages()
1826
if err != nil {
19-
fmt.Printf("%s错误: %v%s\n", utils.ColorRed, err, utils.ColorNC)
27+
fmt.Printf("%s%s: %v%s\n", utils.ColorRed, tr.ErrorTitle, err, utils.ColorNC)
2028
return
2129
}
2230

23-
fmt.Printf("\033[H\033[2J") // 清屏
31+
fmt.Printf("\033[H\033[2J") // Clear screen
2432
fmt.Printf("%s========================================%s\n", utils.ColorBlue, utils.ColorNC)
25-
fmt.Printf("%s Docker 镜像管理%s\n", utils.ColorBlue, utils.ColorNC)
33+
fmt.Printf("%s %s%s\n", utils.ColorBlue, tr.MenuImageManagement, utils.ColorNC)
2634
fmt.Printf("%s========================================%s\n\n", utils.ColorBlue, utils.ColorNC)
2735

28-
// 打印表头
36+
// Print header
2937
fmt.Printf("%-50s %-12s %-12s %-12s %-10s\n", "IMAGE", "ID", "DISK USAGE", "CONTENT SIZE", "EXTRA")
3038
for i, img := range images {
3139
name := img.Name
@@ -42,63 +50,122 @@ func RunImageMenu(dockerCmd *commands.DockerCommand, readInput func(string) stri
4250
i+1, name, img.ID[:12], img.GetDisplaySize(), img.GetDisplayContentSize(), extra)
4351
}
4452

45-
fmt.Printf("\n%s镜像操作%s\n", utils.ColorGreen, utils.ColorNC)
53+
fmt.Printf("\n%s%s%s\n", utils.ColorGreen, tr.MenuFunction, utils.ColorNC)
4654
fmt.Println("------------------------------")
47-
fmt.Println(" 1. 拉取镜像")
48-
fmt.Println(" 2. 删除指定镜像")
49-
fmt.Println(" 3. 删除所有镜像")
55+
fmt.Printf(" 1. %s\n", tr.PullImage)
56+
fmt.Printf(" 2. %s\n", tr.DeleteSpecifiedImage)
57+
fmt.Printf(" 3. %s\n", tr.DeleteAllImages)
5058
fmt.Println("------------------------------")
51-
fmt.Println(" 0. 返回")
59+
fmt.Printf(" s. %s\n", tr.SearchImageTitle)
5260
fmt.Println("------------------------------")
53-
fmt.Print("请选择: ")
61+
fmt.Printf(" 0. %s\n", tr.Return)
62+
fmt.Println("------------------------------")
63+
fmt.Print(tr.InputServiceNameIdx) // Using common prompt
5464

5565
input := readInput("")
5666
input = strings.TrimSpace(input)
5767

58-
if input == "0" || input == "" {
68+
if input == "0" || input == "" || input == "q" {
5969
break
6070
}
6171

72+
if input == "s" || input == "search" {
73+
var lines []string
74+
for i, img := range images {
75+
lines = append(lines, fmt.Sprintf("%d. %s (%s)", i+1, img.Name, img.ID[:12]))
76+
}
77+
result := runFzfSelect(tr.SearchImageTitle, lines)
78+
selected := parseFzfResult(result)
79+
if selected != "" {
80+
// Handle selected image
81+
var idx int
82+
_, _ = fmt.Sscanf(selected, "%d", &idx)
83+
if idx > 0 && idx <= len(images) {
84+
handleImageAction(images[idx-1], tr, readInput, runInteractiveSubprocess, runFzfSelect, parseFzfResult)
85+
}
86+
}
87+
continue
88+
}
89+
6290
switch input {
6391
case "1":
64-
fmt.Print("请输入要拉取的镜像名称 (如 nginx:latest): ")
92+
fmt.Printf("%s: ", tr.PullImage)
6593
name := readInput("")
6694
name = strings.TrimSpace(name)
6795
if name != "" {
68-
fmt.Printf("%s正在拉取镜像 %s...%s\n", utils.ColorYellow, name, utils.ColorNC)
96+
fmt.Printf("%s%s %s...%s\n", utils.ColorYellow, tr.PullImage, name, utils.ColorNC)
6997
cmd := exec.Command("docker", "pull", name)
7098
_ = runInteractiveSubprocess(cmd)
7199
}
72100
case "2":
73-
fmt.Print("请输入要删除的镜像索引: ")
101+
fmt.Print(tr.InputServiceNameIdx)
74102
idxStr := readInput("")
75103
var idx int
76104
_, _ = fmt.Sscanf(strings.TrimSpace(idxStr), "%d", &idx)
77105
if idx > 0 && idx <= len(images) {
78-
img := images[idx-1]
79-
fmt.Printf("%s确定要删除镜像 %s (ID: %s) 吗? (y/n): %s", utils.ColorYellow, img.Name, img.ID[:12], utils.ColorNC)
80-
confirm := readInput("")
81-
if strings.ToLower(strings.TrimSpace(confirm)) == "y" {
82-
fmt.Printf("%s正在删除镜像...%s\n", utils.ColorYellow, utils.ColorNC)
83-
if err := img.Remove(image.RemoveOptions{Force: true}); err != nil {
84-
fmt.Printf("%s失败: %v%s\n", utils.ColorRed, err, utils.ColorNC)
85-
} else {
86-
fmt.Printf("%s成功%s\n", utils.ColorGreen, utils.ColorNC)
87-
}
88-
time.Sleep(1 * time.Second)
89-
}
106+
handleImageAction(images[idx-1], tr, readInput, runInteractiveSubprocess, runFzfSelect, parseFzfResult)
90107
} else {
91-
fmt.Printf("%s无效的索引%s\n", utils.ColorRed, utils.ColorNC)
108+
fmt.Printf("%s%s%s\n", utils.ColorRed, tr.InvalidIndex, utils.ColorNC)
92109
time.Sleep(1 * time.Second)
93110
}
94111
case "3":
95-
fmt.Printf("%s危险: 这将删除所有未被使用的镜像! 是否继续? (y/n): %s", utils.ColorRed, utils.ColorNC)
112+
fmt.Printf("%s%s%s", utils.ColorRed, tr.DangerDeleteAllImages, utils.ColorNC)
96113
confirm := readInput("")
97114
if strings.ToLower(strings.TrimSpace(confirm)) == "y" {
98-
fmt.Printf("%s正在清理所有未使用镜像...%s\n", utils.ColorYellow, utils.ColorNC)
115+
fmt.Printf("%s%s%s\n", utils.ColorYellow, tr.CleaningAllUnusedImages, utils.ColorNC)
99116
cmd := exec.Command("docker", "image", "prune", "-af")
100117
_ = runInteractiveSubprocess(cmd)
101118
}
102119
}
103120
}
104121
}
122+
123+
func handleImageAction(
124+
img *commands.Image,
125+
tr *i18n.TranslationSet,
126+
readInput func(string) string,
127+
runInteractiveSubprocess func(*exec.Cmd) error,
128+
runFzfSelect func(string, []string) string,
129+
parseFzfResult func(string) string,
130+
) {
131+
lines := []string{
132+
fmt.Sprintf("1: %s", tr.RemoveImage),
133+
fmt.Sprintf("2: %s", tr.RunImage),
134+
}
135+
136+
result := runFzfSelect(fmt.Sprintf(tr.SelectActionForImage, img.Name), lines)
137+
actionID := parseFzfResult(result)
138+
139+
switch actionID {
140+
case "1": // Remove
141+
fmt.Printf("%s%s %s (ID: %s) ? (y/n): %s", utils.ColorYellow, tr.ConfirmDeleteImage, img.Name, img.ID[:12], utils.ColorNC)
142+
confirm := readInput("")
143+
if strings.ToLower(strings.TrimSpace(confirm)) == "y" {
144+
fmt.Printf("%s%s%s\n", utils.ColorYellow, tr.DeletingImage, utils.ColorNC)
145+
if err := img.Remove(image.RemoveOptions{Force: true}); err != nil {
146+
fmt.Printf("%s%s: %v%s\n", utils.ColorRed, tr.ActionFailed, err, utils.ColorNC)
147+
} else {
148+
fmt.Printf("%s%s%s\n", utils.ColorGreen, tr.ActionSuccess, utils.ColorNC)
149+
}
150+
time.Sleep(1 * time.Second)
151+
}
152+
case "2": // Run (Interactive)
153+
fmt.Print(tr.InputContainerName)
154+
name := readInput("")
155+
name = strings.TrimSpace(name)
156+
157+
fmt.Printf("%s%s%s\n", utils.ColorBlue, tr.DetectingShell, utils.ColorNC)
158+
// 参考进入容器的做法,通过一个临时容器检测 shell
159+
checkCmd := exec.Command("docker", "run", "--rm", img.ID, "which", "bash")
160+
shell := "sh"
161+
if err := checkCmd.Run(); err == nil {
162+
shell = "bash"
163+
}
164+
165+
cmd := img.GetInteractiveRunCmd(name, shell)
166+
if err := runInteractiveSubprocess(cmd); err != nil {
167+
fmt.Printf("%s%s%s\n", utils.ColorRed, fmt.Sprintf(tr.RunImageFailed, err), utils.ColorNC)
168+
time.Sleep(1 * time.Second)
169+
}
170+
}
171+
}

0 commit comments

Comments
 (0)