Reconnect the app's command channel after sleep; list clients from it

This commit is contained in:
Ian Gulliver
2026-10-18 09:16:31 -07:00
parent 931d1b9a96
commit 1363a3604d
11 changed files with 444 additions and 388 deletions
+4 -11
View File
@@ -10,17 +10,15 @@ import (
"os"
"os/exec"
"path/filepath"
"time"
"github.com/coder/websocket"
"g.fc.run/misc/byre/internal/clients"
)
func (s *Server) ConnectClient(w http.ResponseWriter, r *http.Request) {
id := r.Header.Get("Byre-Client")
host := r.Header.Get("Byre-Host")
if host == "" {
writeError(w, http.StatusBadRequest, errors.New("missing Byre-Host"))
if id == "" || host == "" {
writeError(w, http.StatusBadRequest, errors.New("missing Byre-Client or Byre-Host"))
return
}
@@ -31,12 +29,7 @@ func (s *Server) ConnectClient(w http.ResponseWriter, r *http.Request) {
}
defer ws.CloseNow()
s.clients.Serve(r.Context(), ws, clients.Client{
Login: actor(r),
Host: host,
Commit: r.Header.Get("Byre-Commit"),
ConnectedAt: time.Now().UTC(),
})
s.clients.Serve(r.Context(), ws, id, actor(r), host, r.Header.Get("Byre-Commit"))
}
func (s *Server) GetClientTransfer(w http.ResponseWriter, r *http.Request, transferID string) {
+5 -49
View File
@@ -1,45 +1,25 @@
package server
import (
"sort"
"sync"
"time"
"g.fc.run/misc/byre/internal/api"
)
type clientKey struct{ login, host, commit string }
type hub struct {
mu sync.Mutex
subs map[chan api.Event]api.ConnectedClient
lastAction map[clientKey]time.Time
lastNotified map[clientKey]time.Time
mu sync.Mutex
subs map[chan api.Event]bool
}
func newHub() *hub {
return &hub{subs: map[chan api.Event]api.ConnectedClient{}, lastAction: map[clientKey]time.Time{}, lastNotified: map[clientKey]time.Time{}}
return &hub{subs: map[chan api.Event]bool{}}
}
func (h *hub) acted(login, host, commit string) bool {
h.mu.Lock()
defer h.mu.Unlock()
k := clientKey{login, host, commit}
now := time.Now()
h.lastAction[k] = now
prev, ok := h.lastNotified[k]
if ok && now.Sub(prev) < time.Second {
return false
}
h.lastNotified[k] = now
return true
}
func (h *hub) subscribe(client api.ConnectedClient) chan api.Event {
func (h *hub) subscribe() chan api.Event {
h.mu.Lock()
defer h.mu.Unlock()
ch := make(chan api.Event, 256)
h.subs[ch] = client
h.subs[ch] = true
return ch
}
@@ -72,27 +52,3 @@ func (h *hub) count() int {
defer h.mu.Unlock()
return len(h.subs)
}
func (h *hub) clients() []api.ConnectedClient {
h.mu.Lock()
defer h.mu.Unlock()
oldest := map[clientKey]api.ConnectedClient{}
for _, c := range h.subs {
k := clientKey{c.Login, c.Host, c.Commit}
prev, ok := oldest[k]
if ok && !c.ConnectedAt.Before(prev.ConnectedAt) {
continue
}
oldest[k] = c
}
out := []api.ConnectedClient{}
for k, c := range oldest {
at, ok := h.lastAction[k]
if ok {
c.LastActionAt = &at
}
out = append(out, c)
}
sort.Slice(out, func(i, j int) bool { return out[i].ConnectedAt.Before(out[j].ConnectedAt) })
return out
}
+18 -3
View File
@@ -45,11 +45,11 @@ func New(dataDir string, portFirst int) (*Server, error) {
s := &Server{
hub: newHub(),
clients: clients.New(),
terminals: newTerminals(),
startedAt: time.Now().UTC(),
users: map[string]string{},
}
s.clients = clients.New(s.clientsChanged)
s.store, err = store.Open(filepath.Join(dataDir, "byre.db"), portFirst, s.publish)
if err != nil {
@@ -145,11 +145,26 @@ func (s *Server) identify(next http.Handler) http.Handler {
}
func (s *Server) acted(login string, r *http.Request) {
if s.hub.acted(login, r.Header.Get("Byre-Host"), r.Header.Get("Byre-Commit")) {
if s.clients.Acted(r.Header.Get("Byre-Client"), login, r.Header.Get("Byre-Host"), r.Header.Get("Byre-Commit")) {
s.clientsChanged()
}
}
func (s *Server) connectedClients() []api.ConnectedClient {
out := []api.ConnectedClient{}
for _, c := range s.clients.List() {
out = append(out, api.ConnectedClient{
Id: c.ID,
Login: c.Login,
Host: c.Host,
Commit: c.Commit,
ConnectedAt: c.ConnectedAt,
LastActionAt: c.LastActionAt,
})
}
return out
}
func (s *Server) clientsChanged() {
s.store.Broadcast(api.Event{Kind: "admin.clients", Actor: "byre", Payload: map[string]any{}})
}
@@ -638,7 +653,7 @@ func (s *Server) status(ctx context.Context) (api.Status, error) {
Projects: c.Projects,
ActiveTasks: c.ActiveTasks,
SseClients: s.hub.count(),
Clients: s.hub.clients(),
Clients: s.connectedClients(),
LiveAgents: s.agents.LiveCount(),
RunningTurns: usage.Running,
RateLimits: usage.RateLimits,
+1 -6
View File
@@ -55,12 +55,7 @@ func (s *Server) StreamEvents(w http.ResponseWriter, r *http.Request, params api
w.Header().Set("Cache-Control", "no-cache")
w.WriteHeader(http.StatusOK)
live := s.hub.subscribe(api.ConnectedClient{
Login: viewer,
Host: r.Header.Get("Byre-Host"),
Commit: r.Header.Get("Byre-Commit"),
ConnectedAt: time.Now(),
})
live := s.hub.subscribe()
defer s.clientsChanged()
defer s.hub.unsubscribe(live)
s.clientsChanged()