Files
homarr/tools/homarr-dev/internal/tui/manage.go
T
+12 fcd7c979df feat!: release v2 (#6545)
Co-authored-by: Artur <145776561+arturict@users.noreply.github.com>
Co-authored-by: arturict <arturict@users.noreply.github.com>
Co-authored-by: Thomas <thomas@ajnart.fr>
Co-authored-by: Afonso Jorge Ramos <afonsojorgeramos@gmail.com>
Co-authored-by: Danura30082 <danura30082+github@gmail.com>
Co-authored-by: Kushie <122986913+kushiemoon-dev@users.noreply.github.com>
Co-authored-by: ajnart <thomas@ajnart.fr>
Co-authored-by: ajnart <thomas@ajnart.dev>
Co-authored-by: chenjun <chenjun@foxmail.com>
Co-authored-by: Wikibear <wikibear@users.noreply.github.com>
Co-authored-by: Bergasha <bergasha@gmail.com>
Co-authored-by: leafyelin <43306196+leafyelin@users.noreply.github.com>
Co-authored-by: Lucas Araujo <lucas.lra@gmail.com>
Co-authored-by: GreenMonito <86085570+GreenMonito@users.noreply.github.com>
Co-authored-by: Nerdy-Technician <roffo@nerdy-technician.social>
Co-authored-by: Jun <34545896+jcstrong@users.noreply.github.com>
Co-authored-by: Edmond Abraham <54119952+gitedmond@users.noreply.github.com>
Co-authored-by: thrawnx <tight.bulb9384@fastmail.com>
Co-authored-by: Chris <58918656+nordada@users.noreply.github.com>
Co-authored-by: yi111 <153097222+Yi-111-a@users.noreply.github.com>
Co-authored-by: Yanis <naamaneyanis@gmail.com>
Co-authored-by: Wikibear <me@sej.de>
Co-authored-by: alessiottavo <93441886+alessiottavo@users.noreply.github.com>
2026-10-02 21:00:14 +02:00

176 lines
4.7 KiB
Go

package tui
import (
"fmt"
"charm.land/bubbles/v2/key"
tea "charm.land/bubbletea/v2"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/ui"
)
type manageAction int
const (
actionNone manageAction = iota
actionBuild
actionDeleteImage
actionDeleteData
actionRemoveContainer
actionPrune
)
// confirmation is the armed destructive action awaiting a yes or no.
type confirmation struct {
action manageAction
subject target
prompt string
detail string
}
// handleManageKey is the second half of the `m` chord. Non-destructive actions
// run immediately; anything that deletes state goes through a confirmation, so
// a mistyped chord cannot destroy an instance.
func (m Model) handleManageKey(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
if key.Matches(msg, m.keys.Quit) {
m.tasks.CancelAll()
m.logs.Close()
return m, tea.Quit
}
if key.Matches(msg, m.keys.ManageAbort) || key.Matches(msg, m.keys.Manage) {
m.mode = modeNormal
m.status, m.statusLevel = "manage mode closed", levelInfo
m.relayout()
return m, nil
}
subject, found := m.currentTarget()
if !found {
m.mode = modeNormal
m.relayout()
return m, nil
}
switch {
case key.Matches(msg, m.keys.ManageBuild):
m.mode = modeNormal
m.relayout()
return m.buildLocally(subject)
case key.Matches(msg, m.keys.ManageImage):
if subject.localImage == "" {
m.status, m.statusLevel = subject.label+" has no local image to delete", levelWarn
return m, nil
}
return m.arm(actionDeleteImage, subject)
case key.Matches(msg, m.keys.ManageData):
if subject.volume == "" {
m.status, m.statusLevel = subject.label+" has no data volume to delete", levelWarn
return m, nil
}
return m.arm(actionDeleteData, subject)
case key.Matches(msg, m.keys.ManageStop):
return m.arm(actionRemoveContainer, subject)
case key.Matches(msg, m.keys.ManagePrune):
return m.arm(actionPrune, subject)
}
return m, nil
}
// arm moves into the confirmation mode with a prompt describing exactly what is
// about to be destroyed.
func (m Model) arm(action manageAction, subject target) (Model, tea.Cmd) {
confirm := confirmation{action: action, subject: subject}
switch action {
case actionDeleteImage:
confirm.prompt = "Delete image " + subject.localImage + "?"
confirm.detail = "The image is removed from Docker. Rebuild it later with R."
case actionDeleteData:
confirm.prompt = "Delete data volume " + subject.volume + "?"
confirm.detail = "Boards, users and integrations in this instance are lost. The container is removed first."
if size := m.volumeSize(subject.volume); size != "" {
confirm.detail += " (" + size + " reclaimed)"
}
case actionRemoveContainer:
confirm.prompt = "Remove container " + subject.container + "?"
confirm.detail = "The container is force-removed. Its data volume is kept."
case actionPrune:
stopped := 0
for _, container := range m.containers {
if !container.Running() {
stopped++
}
}
confirm.prompt = fmt.Sprintf("Remove %d stopped Homarr containers?", stopped)
confirm.detail = "Data volumes are kept."
default:
return m, nil
}
m.confirm = confirm
m.mode = modeConfirm
// The confirmation is the only prompt that should be speaking.
m.status = ""
m.relayout()
return m, nil
}
func (m Model) volumeSize(name string) string {
for _, volume := range m.volumes {
if volume.Name == name {
return volume.Size
}
}
return ""
}
func (m Model) handleConfirmKey(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
switch {
case key.Matches(msg, m.keys.ManageAbort):
m.mode = modeNormal
m.confirm = confirmation{}
m.status, m.statusLevel = "canceled", levelInfo
m.relayout()
return m, nil
case key.Matches(msg, m.keys.ManageConfirm):
confirm := m.confirm
m.mode = modeNormal
m.confirm = confirmation{}
m.relayout()
switch confirm.action {
case actionDeleteImage:
return m.deleteImage(confirm.subject.localImage)
case actionDeleteData:
return m.deleteData(confirm.subject)
case actionRemoveContainer:
return m.removeContainer(confirm.subject.container)
case actionPrune:
return m.pruneStopped()
}
return m, nil
}
return m, nil
}
// manageBar is the hint strip shown while the chord is armed.
func (m Model) manageBar() string {
subject, found := m.currentTarget()
label := "selection"
if found {
label = subject.label
}
return clip(ui.ModeBadge("MANAGE", ui.Warning)+" "+
ui.Body.Render(label)+" "+
m.helpView.ShortHelpView(m.keys.ManageHelp()), m.width)
}
// confirmBar is the destructive-action prompt. It is deliberately loud.
func (m Model) confirmBar() string {
prompt := ui.ModeBadge("CONFIRM", ui.Danger) + " " + ui.Heading.Render(m.confirm.prompt)
detail := ui.Help.Render(m.confirm.detail + " y confirm · n cancel")
return clip(prompt, m.width) + "\n" + clip(detail, m.width)
}