Live page: streamed updates and a sliding chart

The server pushes each new step over server-sent events
(GET /api/v1/live/stream) the moment it is sampled, so updates no
longer arrive in uneven pairs. The chart slides left steadily between
steps instead of jumping, and the big numbers count to their new
value. Both stay still with reduced motion.
This commit is contained in:
Daniel Redetzke
2026-10-05 14:39:35 +03:00
parent 5850f3976e
commit dbd1808803
6 changed files with 217 additions and 57 deletions
+28 -1
View File
@@ -30,12 +30,32 @@ type Speeds struct {
last map[string][2]int64 // raw rx, tx by public key
lastAt time.Time
points []SpeedPoint
subs map[chan SpeedPoint]struct{}
done chan struct{} // closed when Run returns
}
func newSpeeds(store *Store, kernel Kernel) *Speeds {
return &Speeds{store: store, kernel: kernel, last: map[string][2]int64{}}
return &Speeds{store: store, kernel: kernel, last: map[string][2]int64{}, subs: map[chan SpeedPoint]struct{}{}, done: make(chan struct{})}
}
// Subscribe returns the current points and a channel that receives each new
// one; cancel ends the subscription. A subscriber that falls behind misses
// points rather than holding up the sampler.
func (s *Speeds) Subscribe() (points []SpeedPoint, ch <-chan SpeedPoint, cancel func()) {
c := make(chan SpeedPoint, 4)
s.mu.Lock()
defer s.mu.Unlock()
s.subs[c] = struct{}{}
return append([]SpeedPoint{}, s.points...), c, func() {
s.mu.Lock()
delete(s.subs, c)
s.mu.Unlock()
}
}
// Done is closed when the sampler stops, so streams can end.
func (s *Speeds) Done() <-chan struct{} { return s.done }
func (s *Speeds) sample(now time.Time) {
cfg := s.store.Get()
samples, err := s.kernel.Sample(cfg.Server.Interface)
@@ -77,6 +97,12 @@ func (s *Speeds) sample(now time.Time) {
if len(s.points) > speedPoints {
s.points = s.points[len(s.points)-speedPoints:]
}
for c := range s.subs {
select {
case c <- pt:
default:
}
}
}
// Since returns the points newer than the unix time t, oldest first.
@@ -93,6 +119,7 @@ func (s *Speeds) Since(t int64) []SpeedPoint {
}
func (s *Speeds) Run(stop <-chan struct{}) {
defer close(s.done)
s.sample(time.Now())
t := time.NewTicker(speedStep)
defer t.Stop()