159 lines
4.2 KiB
Go
159 lines
4.2 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
tea "charm.land/bubbletea/v2"
|
|
|
|
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/audio"
|
|
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/docker"
|
|
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/i18n"
|
|
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/stack"
|
|
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/ui/theme"
|
|
)
|
|
|
|
// stacksMsg — hasil scan stack dari disk + rekonsiliasi compose ls.
|
|
type stacksMsg struct {
|
|
id int64
|
|
items []stack.Stack
|
|
err error
|
|
}
|
|
|
|
// fetchStacks — scan folder stack (synch cepat) lalu overlay status dari
|
|
// `compose ls`. Compose gagal → status disk tetap dipakai (degrade anggun).
|
|
func fetchStacks(id int64, cm *docker.ComposeClient, dir string) tea.Cmd {
|
|
return func() tea.Msg {
|
|
items, err := stack.ScanFS(dir)
|
|
if err != nil {
|
|
return stacksMsg{id: id, err: err}
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
if projects, err := cm.List(ctx); err == nil {
|
|
items = stack.Reconcile(items, projects)
|
|
}
|
|
return stacksMsg{id: id, items: items}
|
|
}
|
|
}
|
|
|
|
// stackActionMsg — hasil menjalankan aksi compose untuk sebuah stack.
|
|
type stackActionMsg struct {
|
|
id int64
|
|
name string
|
|
label string
|
|
out string
|
|
err error
|
|
}
|
|
|
|
// mapActionName — kode tombol → nama aksi (kunci katalog stack.*).
|
|
func mapActionName(k string) string {
|
|
switch k {
|
|
case "u":
|
|
return "up"
|
|
case "s":
|
|
return "stop"
|
|
case "R":
|
|
return "restart"
|
|
case "p":
|
|
return "pull"
|
|
default:
|
|
return "down"
|
|
}
|
|
}
|
|
|
|
// beginStackAction — jalankan aksi async untuk stack terpilih (toolbar/keys).
|
|
func (m *Model) beginStackAction(act string) tea.Cmd {
|
|
if m.section != sectionStacks || m.view != viewList || m.sectionLen() == 0 || m.stackBusy != "" {
|
|
return nil
|
|
}
|
|
s := m.stacks[min(m.cur(), m.sectionLen()-1)]
|
|
m.stackActID++
|
|
m.stackBusy = s.Name
|
|
m.stackBusyAct = act
|
|
m.sound.Play(audio.Exec)
|
|
return runStackAction(m.stackActID, m.compose, s, act)
|
|
}
|
|
|
|
// runStackAction — eksekusi verb compose (perbarui = pull lalu up).
|
|
func runStackAction(id int64, cm *docker.ComposeClient, s stack.Stack, act string) tea.Cmd {
|
|
return func() tea.Msg {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
var out string
|
|
var err error
|
|
switch act {
|
|
case "u":
|
|
out, err = cm.Up(ctx, s.Dir)
|
|
case "s":
|
|
out, err = cm.Stop(ctx, s.Dir)
|
|
case "R":
|
|
out, err = cm.Restart(ctx, s.Dir)
|
|
case "p": // perbarui: pull image terbaru lalu recreate
|
|
out, err = cm.Pull(ctx, s.Dir)
|
|
if err == nil {
|
|
var upOut string
|
|
upOut, err = cm.Up(ctx, s.Dir)
|
|
out += upOut
|
|
}
|
|
case "D":
|
|
out, err = cm.Down(ctx, s.Dir)
|
|
}
|
|
return stackActionMsg{id: id, name: s.Name, label: i18n.T("stack." + mapActionName(act)), out: out, err: err}
|
|
}
|
|
}
|
|
|
|
// stackToolbar — tombol aksi (klikable via keyZones — sama dengan footer).
|
|
func (m Model) stackToolbar() string {
|
|
keys := []string{"u", "s", "R", "p", "D"}
|
|
parts := make([]string, 0, len(keys))
|
|
for _, k := range keys {
|
|
parts = append(parts, "["+k+"] "+i18n.T("stack."+mapActionName(k)))
|
|
}
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
// stackConfirmLine — tombol konfirmasi destruktif (klikable).
|
|
func (m Model) stackConfirmLine() string {
|
|
return "[y] " + i18n.T("stack.yes") + " [n] " + i18n.T("stack.no")
|
|
}
|
|
|
|
// lastLine — baris terakhir output (untuk ringkasan toast).
|
|
func lastLine(s string) string {
|
|
lines := strings.Split(strings.TrimSpace(s), "\n")
|
|
if len(lines) == 0 {
|
|
return ""
|
|
}
|
|
return truncate(lines[len(lines)-1], 120)
|
|
}
|
|
|
|
// stackStyle — glyph + warna per status stack.
|
|
func stackStyle(st stack.Status) (string, theme.Color) {
|
|
th := theme.Current()
|
|
switch st {
|
|
case stack.StatusRunning:
|
|
return "●", th.Success
|
|
case stack.StatusExited:
|
|
return "○", th.Danger
|
|
case stack.StatusCreatedFile, stack.StatusCreatedStack:
|
|
return "◌", th.Info
|
|
default:
|
|
return "○", th.Overlay
|
|
}
|
|
}
|
|
|
|
// stackStatusText — label terjemahan status stack.
|
|
func stackStatusText(st stack.Status) string {
|
|
switch st {
|
|
case stack.StatusRunning:
|
|
return i18n.T("state.running")
|
|
case stack.StatusExited:
|
|
return i18n.T("state.exited")
|
|
case stack.StatusCreatedStack:
|
|
return i18n.T("state.created")
|
|
default:
|
|
return i18n.T("state.detected")
|
|
}
|
|
}
|