Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_de.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>m</kbd>: zeige Protokolle
<kbd>E</kbd>: exec shell
<kbd>c</kbd>: führe vordefinierten benutzerdefinierten Befehl aus
<kbd>y</kbd>: Container-ID kopieren
<kbd>b</kbd>: view bulk commands
<kbd>w</kbd>: open in browser (first port is http)
<kbd>enter</kbd>: fokussieren aufs Hauptpanel
Expand Down Expand Up @@ -62,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>c</kbd>: führe vordefinierten benutzerdefinierten Befehl aus
<kbd>y</kbd>: Image-Namen kopieren
<kbd>d</kbd>: entferne Image
<kbd>b</kbd>: view bulk commands
<kbd>enter</kbd>: fokussieren aufs Hauptpanel
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>m</kbd>: view logs
<kbd>E</kbd>: exec shell
<kbd>c</kbd>: run predefined custom command
<kbd>y</kbd>: copy container id
<kbd>b</kbd>: view bulk commands
<kbd>w</kbd>: open in browser (first port is http)
<kbd>enter</kbd>: focus main panel
Expand Down Expand Up @@ -62,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>c</kbd>: run predefined custom command
<kbd>y</kbd>: copy image name
<kbd>d</kbd>: remove image
<kbd>b</kbd>: view bulk commands
<kbd>enter</kbd>: focus main panel
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_es.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>m</kbd>: ver logs
<kbd>E</kbd>: ejecutar shell
<kbd>c</kbd>: ejecutar comando personalizado
<kbd>y</kbd>: Copiar ID del contenedor
<kbd>b</kbd>: ver comandos masivos
<kbd>w</kbd>: abrir en navegador (first port is http)
<kbd>enter</kbd>: enfocar panel principal
Expand Down Expand Up @@ -62,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>c</kbd>: ejecutar comando personalizado
<kbd>y</kbd>: Copiar nombre de la imagen
<kbd>d</kbd>: limpiar imagen
<kbd>b</kbd>: ver comandos masivos
<kbd>enter</kbd>: enfocar panel principal
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>m</kbd>: voir les enregistrements
<kbd>E</kbd>: exécuter le shell
<kbd>c</kbd>: exécuter une commande prédéfinie
<kbd>y</kbd>: Copier l'ID du conteneur
<kbd>b</kbd>: voir les commandes groupées
<kbd>w</kbd>: ouvrir dans le navigateur (le premier port est http)
<kbd>enter</kbd>: focus panneau principal
Expand Down Expand Up @@ -62,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>c</kbd>: exécuter une commande prédéfinie
<kbd>y</kbd>: Copier le nom de l'image
<kbd>d</kbd>: supprimer l'image
<kbd>b</kbd>: voir les commandes groupées
<kbd>enter</kbd>: focus panneau principal
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_nl.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>m</kbd>: bekijk logs
<kbd>E</kbd>: exec shell
<kbd>c</kbd>: draai een vooraf bedacht aangepaste opdracht
<kbd>y</kbd>: Kopieer container-id
<kbd>b</kbd>: view bulk commands
<kbd>w</kbd>: open in browser (first port is http)
<kbd>enter</kbd>: focus hoofdpaneel
Expand Down Expand Up @@ -62,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>c</kbd>: draai een vooraf bedacht aangepaste opdracht
<kbd>y</kbd>: Kopieer image-naam
<kbd>d</kbd>: verwijder image
<kbd>b</kbd>: view bulk commands
<kbd>enter</kbd>: focus hoofdpaneel
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>m</kbd>: pokaż logi
<kbd>E</kbd>: exec shell
<kbd>c</kbd>: wykonaj predefiniowaną własną komende
<kbd>y</kbd>: Kopiuj identyfikator kontenera
<kbd>b</kbd>: view bulk commands
<kbd>w</kbd>: open in browser (first port is http)
<kbd>enter</kbd>: skup na głównym panelu
Expand Down Expand Up @@ -62,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>c</kbd>: wykonaj predefiniowaną własną komende
<kbd>y</kbd>: Kopiuj nazwę obrazu
<kbd>d</kbd>: usuń obraz
<kbd>b</kbd>: view bulk commands
<kbd>enter</kbd>: skup na głównym panelu
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_pt.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>m</kbd>: ver logs
<kbd>E</kbd>: executar shell
<kbd>c</kbd>: executar comando personalizado predefinido
<kbd>y</kbd>: Copiar ID do contêiner
<kbd>b</kbd>: ver comandos em massa
<kbd>w</kbd>: abrir no navegador (primeira porta é http)
<kbd>enter</kbd>: focar no painel principal
Expand Down Expand Up @@ -62,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>c</kbd>: executar comando personalizado predefinido
<kbd>y</kbd>: Copiar nome da imagem
<kbd>d</kbd>: remover imagem
<kbd>b</kbd>: ver comandos em massa
<kbd>enter</kbd>: focar no painel principal
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_tr.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>m</kbd>: kayıt defterini görüntüle
<kbd>E</kbd>: exec shell
<kbd>c</kbd>: önceden tanımlanmış özel komutu çalıştır
<kbd>y</kbd>: Konteyner kimliğini kopyala
<kbd>b</kbd>: view bulk commands
<kbd>w</kbd>: open in browser (first port is http)
<kbd>enter</kbd>: ana panele odaklan
Expand Down Expand Up @@ -62,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>c</kbd>: önceden tanımlanmış özel komutu çalıştır
<kbd>y</kbd>: Görüntü adını kopyala
<kbd>d</kbd>: imajı kaldır
<kbd>b</kbd>: view bulk commands
<kbd>enter</kbd>: ana panele odaklan
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>m</kbd>: 查看日志
<kbd>E</kbd>: 执行shell
<kbd>c</kbd>: 运行预定义的自定义命令
<kbd>y</kbd>: 复制容器 ID
<kbd>b</kbd>: 查看批量命令
<kbd>w</kbd>: 在浏览器中打开(第一个端口为http)
<kbd>enter</kbd>: 聚焦主面板
Expand Down Expand Up @@ -62,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>c</kbd>: 运行预定义的自定义命令
<kbd>y</kbd>: 复制镜像名称
<kbd>d</kbd>: 移除镜像
<kbd>b</kbd>: 查看批量命令
<kbd>enter</kbd>: 聚焦主面板
Expand Down
78 changes: 78 additions & 0 deletions pkg/commands/clipboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package commands

import (
"context"
"fmt"
"io"
"os/exec"
"time"
)

const clipboardTimeout = 1500 * time.Millisecond

// CopyToClipboard copies the given string to the system clipboard.
func (c *OSCommand) CopyToClipboard(s string) error {
if s == "" {
return fmt.Errorf("nothing to copy")
}

switch c.Platform.os {
case "darwin":
ctx, cancel := context.WithTimeout(context.Background(), clipboardTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, "pbcopy")
return runCmdWithInput(cmd, s)
case "linux":
if p, err := exec.LookPath("wl-copy"); err == nil {
ctx, cancel := context.WithTimeout(context.Background(), clipboardTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, p)
return runCmdWithInput(cmd, s)
}
if p, err := exec.LookPath("xclip"); err == nil {
ctx, cancel := context.WithTimeout(context.Background(), clipboardTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, p, "-selection", "clipboard")
return runCmdWithInput(cmd, s)
}
if p, err := exec.LookPath("xsel"); err == nil {
ctx, cancel := context.WithTimeout(context.Background(), clipboardTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, p, "--clipboard", "--input")
return runCmdWithInput(cmd, s)
}
return fmt.Errorf("no clipboard utility found: try installing wl-clipboard (wl-copy) or xclip/xsel")
case "windows":
ctx, cancel := context.WithTimeout(context.Background(), clipboardTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, "powershell", "-NoProfile", "-Command", "Set-Clipboard")
return runCmdWithInput(cmd, s)
default:
return fmt.Errorf("unsupported platform: %s", c.Platform.os)
}
}

func runCmdWithInput(cmd *exec.Cmd, s string) error {
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}

if err := cmd.Start(); err != nil {
_ = stdin.Close()
return err
}

if _, err := io.WriteString(stdin, s); err != nil {
_ = cmd.Process.Kill()
_ = stdin.Close()
return err
}

if err := stdin.Close(); err != nil {
_ = cmd.Process.Kill()
return err
}

return cmd.Wait()
}
44 changes: 44 additions & 0 deletions pkg/gui/app_status_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@ func (m *statusManager) addWaitingStatus(name string) {
m.statuses = append([]appStatus{newStatus}, m.statuses...)
}

// addMessage adds a transient message status that will be removed after the given duration.
func (m *statusManager) addMessage(name string, duration time.Duration) {
m.removeStatus(name)
newStatus := appStatus{
name: name,
statusType: "message",
duration: int(duration / time.Millisecond),
}
m.statuses = append([]appStatus{newStatus}, m.statuses...)

// schedule removal after duration
go func() {
time.Sleep(duration)
m.removeStatus(name)
}()
}

func (m *statusManager) getStatusString() string {
if len(m.statuses) == 0 {
return ""
Expand Down Expand Up @@ -80,3 +97,30 @@ func (gui *Gui) WithWaitingStatus(name string, f func() error) error {

return nil
}

// AddMessage shows a transient message in the appStatus area for the given duration.
func (gui *Gui) AddMessage(name string, duration time.Duration) {
if gui.statusManager == nil {
return
}
gui.statusManager.addMessage(name, duration)
// ensure the UI is updated immediately
go func() {
// repeatedly render until the message expires so layout picks it up
ticker := time.NewTicker(time.Millisecond * 50)
defer ticker.Stop()
start := time.Now()
for range ticker.C {
if time.Since(start) > duration+time.Millisecond*100 {
return
}
appStatus := gui.statusManager.getStatusString()
if appStatus == "" {
return
}
if err := gui.renderString(gui.g, "appStatus", appStatus); err != nil {
gui.Log.Warn(err)
}
}
}()
}
45 changes: 45 additions & 0 deletions pkg/gui/clipboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package gui

import (
"fmt"
"time"
)

// CopyToClipboard copies s to the system clipboard and shows a temporary status message
func (gui *Gui) CopyToClipboard(s string) error {
if err := gui.OSCommand.CopyToClipboard(s); err != nil {
return err
}

// show a short status message in the appStatus area
// try to be slightly more informative if possible
msg := gui.Tr.CopiedFmt
copiedContent := ""
if len(s) > 0 {
// show up to 50 chars of the copied content
short := s
if len(short) > 50 {
short = short[:50] + "..."
}
copiedContent = short
}
// format using translation (if CopiedFmt is empty fall back)
if gui.Tr.CopiedFmt != "" {
if copiedContent != "" {
msg = fmt.Sprintf(gui.Tr.CopiedFmt, copiedContent)
} else {
msg = fmt.Sprintf(gui.Tr.CopiedFmt, "")
}
} else {
if copiedContent != "" {
msg = "Copied: " + copiedContent
} else {
msg = "Copied to clipboard"
}
}

// show the message through statusManager so it doesn't get clobbered by other status updates
gui.AddMessage(msg, 2*time.Second)

return nil
}
18 changes: 18 additions & 0 deletions pkg/gui/containers_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,24 @@ func (gui *Gui) handleContainerViewLogs(g *gocui.Gui, v *gocui.View) error {
return nil
}

func (gui *Gui) handleCopyContainerID(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
return nil
}

id := ctr.ID
if len(id) >= 12 {
id = id[:12]
}

if err := gui.CopyToClipboard(id); err != nil {
return gui.createErrorPanel(err.Error())
}

return nil
}

func (gui *Gui) handleContainersExecShell(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
Expand Down
26 changes: 26 additions & 0 deletions pkg/gui/images_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,29 @@ func (gui *Gui) handleImagesBulkCommand(g *gocui.Gui, v *gocui.View) error {

return gui.createBulkCommandMenu(bulkCommands, commandObject)
}

func (gui *Gui) handleCopyImageName(g *gocui.Gui, v *gocui.View) error {
img, err := gui.Panels.Images.GetSelectedItem()
if err != nil {
return nil
}

name := img.Name
if img.Tag != "" {
name = name + ":" + img.Tag
}

if name == "none" || name == ":" {
if len(img.ID) >= 12 {
name = img.ID[:12]
} else {
name = img.ID
}
}

if err := gui.CopyToClipboard(name); err != nil {
return gui.createErrorPanel(err.Error())
}

return nil
}
Loading