feat(events): listener Docker events (F-002) — auto-refresh daftar + panel Aktivitas beranda hidup
CI / test (push) Successful in 25s
CI / test (push) Successful in 25s
This commit is contained in:
1 parent
177e27910b
commit
f555f27467
8 files changed
+316
-32
No files matched your search
@@ -0,0 +1,94 @@
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user