package app import ( "context" "strings" "time" tea "charm.land/bubbletea/v2" "gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/docker" "gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/i18n" ) // eventMsg — satu event Docker dari stream (volume rendah; 1 event = 1 render). type eventMsg struct { id int64 ev docker.Event } type eventsClosedMsg struct{ id int64 } type eventsRetryMsg struct{ id int64 } type refreshTickMsg struct{ gen int64 } const ( activityMax = 20 refreshDebounce = 500 * time.Millisecond eventsRetryDelay = 5 * time.Second ) // startEvents — (re)buka stream event daemon. Dipanggil dari New (init), `r`, // dan retry otomatis saat koneksi putus. func (m *Model) startEvents() tea.Cmd { if m.eventsCancel != nil { m.eventsCancel() m.eventsCancel = nil } m.eventsCh = nil if m.cl == nil { return nil } m.eventsID++ ctx, cancel := context.WithCancel(context.Background()) m.eventsCancel = cancel m.eventsCh = m.cl.StreamEvents(ctx) return waitEvent(m.eventsID, m.eventsCh) } func waitEvent(id int64, ch <-chan docker.Event) tea.Cmd { return func() tea.Msg { ev, ok := <-ch if !ok { return eventsClosedMsg{id: id} } return eventMsg{id: id, ev: ev} } } func refreshAfter(gen int64) tea.Cmd { return tea.Tick(refreshDebounce, func(time.Time) tea.Msg { return refreshTickMsg{gen: gen} }) } func eventsRetry(id int64) tea.Cmd { return tea.Tick(eventsRetryDelay, func(time.Time) tea.Msg { return eventsRetryMsg{id: id} }) } // evTypeKey — jenis event → kunci katalog bagian (untuk label terjemahan). func evTypeKey(t string) string { switch t { case "image": return "images" case "volume": return "volumes" case "network": return "networks" default: return "containers" } } // activityLines — event terbaru di atas (untuk panel Aktivitas di beranda). func (m Model) activityLines(w, h int) []string { if len(m.activity) == 0 { return []string{dim(i18n.T("activity.waiting"))} } out := make([]string, 0, h) for i := len(m.activity) - 1; i >= 0 && len(out) < h; i-- { ev := m.activity[i] label := strings.ToLower(i18n.T("section." + evTypeKey(ev.Type))) out = append(out, dim(ev.At.Format("15:04:05"))+" "+ dim("["+label+"]")+" "+ text("'"+truncate(ev.Actor, 20)+"'")+" "+ dim(truncate(ev.Action, 24))) } return out }