rename: bridges/valves → tunnels (one term across types + API + UI)
API shape:
GET /api/connections → GET /api/tunnels
body: {"connections": […]} → {"tunnels": […]}
Type rename (package stays "bridge" — internal):
Valve → Listener
clientBridge → tunnel
ConnSnapshot → TunnelSnapshot
Log messages mirror the new vocab ("listener open/close", "tunnel
open/idle evict/forward failed"). UI header is now "Active tunnels"
and the empty state reads "no active tunnels".
server-manager's dashboard polls /infra/svc-proxy/api/tunnels and
shows "N tunnels" on the svc-proxy infra card.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+38
-37
@@ -5,61 +5,62 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// counters is the per-bridge byte tally. Updated from the two hot paths
|
||||
// counters is the per-tunnel byte tally. Updated from the two hot paths
|
||||
// (readLoop client→backend, readBackend backend→client) — atomic to avoid
|
||||
// locking the bridge for every datagram.
|
||||
// locking the tunnel for every datagram.
|
||||
type counters struct {
|
||||
bytesUp atomic.Uint64 // client → backend
|
||||
bytesDown atomic.Uint64 // backend → client
|
||||
}
|
||||
|
||||
// ConnSnapshot is one row of the active-connections table the UI renders.
|
||||
// All times are wall-clock; sizes are total bytes since the bridge opened.
|
||||
type ConnSnapshot struct {
|
||||
Server string `json:"server"` // pg row name (e.g. "gtnh")
|
||||
Port int `json:"port"` // public UDP port (the valve)
|
||||
Backend string `json:"backend"` // backend addr
|
||||
Client string `json:"client"` // source IP:port
|
||||
BytesUp uint64 `json:"bytes_up"` // client → backend
|
||||
BytesDown uint64 `json:"bytes_down"` // backend → client
|
||||
OpenedAt time.Time `json:"opened_at"` // bridge creation
|
||||
LastSeen time.Time `json:"last_seen"` // most-recent datagram either direction
|
||||
IdleSeconds float64 `json:"idle_seconds"` // derived; UI sorts by this
|
||||
// TunnelSnapshot is one row of the tunnels table the UI renders. All times
|
||||
// are wall-clock; sizes are total bytes since the tunnel opened.
|
||||
type TunnelSnapshot struct {
|
||||
Server string `json:"server"` // pg row name (e.g. "gtnh")
|
||||
Port int `json:"port"` // public UDP port
|
||||
Backend string `json:"backend"` // backend addr
|
||||
Client string `json:"client"` // source IP:port
|
||||
BytesUp uint64 `json:"bytes_up"` // client → backend
|
||||
BytesDown uint64 `json:"bytes_down"` // backend → client
|
||||
OpenedAt time.Time `json:"opened_at"` // tunnel creation
|
||||
LastSeen time.Time `json:"last_seen"` // most-recent datagram either direction
|
||||
IdleSeconds float64 `json:"idle_seconds"` // derived; UI sorts by this
|
||||
}
|
||||
|
||||
// Snapshot returns one row per active client bridge across all valves.
|
||||
// Cheap-ish: takes the Manager lock + each Valve lock briefly, no per-bridge
|
||||
// lock (counters are atomic; LastSeen is read under the bridge lock).
|
||||
func (m *Manager) Snapshot() []ConnSnapshot {
|
||||
// Snapshot returns one row per active per-client tunnel across all
|
||||
// listeners. Cheap-ish: takes the Manager lock + each Listener lock briefly,
|
||||
// no per-tunnel lock (counters are atomic; LastSeen is read under the
|
||||
// tunnel lock).
|
||||
func (m *Manager) Snapshot() []TunnelSnapshot {
|
||||
m.mu.Lock()
|
||||
valves := make([]*Valve, 0, len(m.valves))
|
||||
for _, v := range m.valves {
|
||||
valves = append(valves, v)
|
||||
listeners := make([]*Listener, 0, len(m.listeners))
|
||||
for _, l := range m.listeners {
|
||||
listeners = append(listeners, l)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
var out []ConnSnapshot
|
||||
for _, v := range valves {
|
||||
v.mu.Lock()
|
||||
for _, b := range v.bridges {
|
||||
b.mu.Lock()
|
||||
lastSeen := b.lastSeen
|
||||
opened := b.openedAt
|
||||
b.mu.Unlock()
|
||||
out = append(out, ConnSnapshot{
|
||||
Server: v.route.Name,
|
||||
Port: v.route.Port,
|
||||
Backend: v.route.Address,
|
||||
Client: b.client.String(),
|
||||
BytesUp: b.counters.bytesUp.Load(),
|
||||
BytesDown: b.counters.bytesDown.Load(),
|
||||
var out []TunnelSnapshot
|
||||
for _, l := range listeners {
|
||||
l.mu.Lock()
|
||||
for _, t := range l.tunnels {
|
||||
t.mu.Lock()
|
||||
lastSeen := t.lastSeen
|
||||
opened := t.openedAt
|
||||
t.mu.Unlock()
|
||||
out = append(out, TunnelSnapshot{
|
||||
Server: l.route.Name,
|
||||
Port: l.route.Port,
|
||||
Backend: l.route.Address,
|
||||
Client: t.client.String(),
|
||||
BytesUp: t.counters.bytesUp.Load(),
|
||||
BytesDown: t.counters.bytesDown.Load(),
|
||||
OpenedAt: opened,
|
||||
LastSeen: lastSeen,
|
||||
IdleSeconds: now.Sub(lastSeen).Seconds(),
|
||||
})
|
||||
}
|
||||
v.mu.Unlock()
|
||||
l.mu.Unlock()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user