Files
homarr/tools/homarr-dev/internal/tui/data.go

156 lines
4.2 KiB
Go

package tui
import (
"context"
"sync"
"time"
tea "charm.land/bubbletea/v2"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/docker"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/gh"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/logs"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/registry"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/task"
)
// loadedMsg carries a full refresh. Every source is fetched concurrently and
// each failure is reported separately, so a missing GitHub CLI degrades the
// pull-request list without hiding local images.
type loadedMsg struct {
generation int
prs []gh.PR
images []docker.Image
containers []docker.Container
volumes []docker.Volume
tags map[string]bool
prErr error
imageErr error
tagErr error
}
// containersMsg is the cheap periodic refresh; it skips GitHub and the registry.
type containersMsg struct {
generation int
containers []docker.Container
volumes []docker.Volume
}
type (
tasksChangedMsg struct{}
logsChangedMsg struct{}
ciChecksMsg struct {
pr int
checks []gh.Check
err error
}
pollCIChecksMsg struct {
pr int
}
frameMsg time.Time
refreshMsg time.Time
statusMsg struct {
text string
level statusLevel
}
)
const (
loadTimeout = 45 * time.Second
refreshEvery = 3 * time.Second
frameEvery = 90 * time.Millisecond
prListingLimit = 60
)
// loadAll fetches every data source in parallel and reports them as one
// message, so the table is only rebuilt once per refresh.
func loadAll(generation int, includeBots, refresh bool) tea.Cmd {
return func() tea.Msg {
ctx, cancel := context.WithTimeout(context.Background(), loadTimeout)
defer cancel()
var (
group sync.WaitGroup
message = loadedMsg{generation: generation}
)
group.Add(3)
go func() {
defer group.Done()
if refresh {
message.prs, message.prErr = gh.RefreshPRs(ctx, prListingLimit, includeBots)
return
}
message.prs, message.prErr = gh.ListPRs(ctx, prListingLimit, includeBots)
}()
go func() {
defer group.Done()
message.images, message.imageErr = docker.ListLocalImages(ctx)
}()
go func() {
defer group.Done()
message.tags, message.tagErr = registry.PublishedTags(ctx, refresh)
}()
group.Wait()
message.containers, _ = docker.List()
message.volumes, _ = docker.ListVolumes(ctx, true)
return message
}
}
// loadContainers is the lightweight poll that keeps running state current
// without paying for GitHub, registry or disk-usage round trips.
func loadContainers(generation int) tea.Cmd {
return func() tea.Msg {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
containers, _ := docker.List()
volumes, _ := docker.ListVolumes(ctx, false)
return containersMsg{generation: generation, containers: containers, volumes: volumes}
}
}
// waitForTasks parks a goroutine on the task manager's coalescing wake-up
// channel. Exactly one of these is in flight at a time; each wake-up re-arms
// itself, which is what lets background work drive redraws without polling.
func waitForTasks(manager *task.Manager) tea.Cmd {
return func() tea.Msg {
<-manager.Changed()
return tasksChangedMsg{}
}
}
// waitForLogs mirrors waitForTasks for the live container log streams.
func waitForLogs(registry *logs.Registry) tea.Cmd {
return func() tea.Msg {
<-registry.Changed()
return logsChangedMsg{}
}
}
func tickRefresh() tea.Cmd {
return tea.Tick(refreshEvery, func(now time.Time) tea.Msg { return refreshMsg(now) })
}
// tickFrame drives spinner and progress-bar animation. It only runs while
// something is actually moving, so an idle CLI costs no wake-ups at all.
func tickFrame() tea.Cmd {
return tea.Tick(frameEvery, func(now time.Time) tea.Msg { return frameMsg(now) })
}
func notify(text string, level statusLevel) tea.Cmd {
return func() tea.Msg { return statusMsg{text: text, level: level} }
}
func fetchCIChecks(pr int, refresh bool) tea.Cmd {
if pr <= 0 {
return nil
}
return func() tea.Msg {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
checks, err := gh.GetPRChecks(ctx, pr, refresh)
return ciChecksMsg{pr: pr, checks: checks, err: err}
}
}