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:
@@ -40,7 +40,7 @@ func New(addr string, mgr *bridge.Manager, bus *LogBus) *Server {
|
||||
panic(err) // embed.FS misconfigured at build time
|
||||
}
|
||||
mux.Handle("GET /", http.FileServer(http.FS(sub)))
|
||||
mux.HandleFunc("GET /api/connections", s.handleConnections)
|
||||
mux.HandleFunc("GET /api/tunnels", s.handleTunnels)
|
||||
mux.HandleFunc("GET /api/logs", sseLogs(bus))
|
||||
|
||||
s.srv = &http.Server{
|
||||
@@ -66,13 +66,13 @@ func (s *Server) Run(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleConnections(w http.ResponseWriter, _ *http.Request) {
|
||||
func (s *Server) handleTunnels(w http.ResponseWriter, _ *http.Request) {
|
||||
snap := s.mgr.Snapshot()
|
||||
// Sort by most-recently-active first so the UI can render top-down.
|
||||
sort.Slice(snap, func(i, j int) bool { return snap[i].LastSeen.After(snap[j].LastSeen) })
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"connections": snap,
|
||||
"at": time.Now(),
|
||||
"tunnels": snap,
|
||||
"at": time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</header>
|
||||
<main>
|
||||
<section>
|
||||
<h2><span>Active connections <span class="count" id="conn-count"></span></span></h2>
|
||||
<h2><span>Active tunnels <span class="count" id="conn-count"></span></span></h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -66,7 +66,7 @@
|
||||
</thead>
|
||||
<tbody id="conn-rows"></tbody>
|
||||
</table>
|
||||
<div class="empty" id="conn-empty">no active bridges</div>
|
||||
<div class="empty" id="conn-empty">no active tunnels</div>
|
||||
</section>
|
||||
<section>
|
||||
<h2><span>Logs</span><button onclick="document.getElementById('logbox').innerHTML=''">clear</button></h2>
|
||||
@@ -88,20 +88,20 @@ const fmtAgo = secs => {
|
||||
};
|
||||
async function refreshConnections() {
|
||||
try {
|
||||
const r = await fetch('./api/connections');
|
||||
const r = await fetch('./api/tunnels');
|
||||
const j = await r.json();
|
||||
const rows = document.getElementById('conn-rows');
|
||||
const empty = document.getElementById('conn-empty');
|
||||
const count = document.getElementById('conn-count');
|
||||
rows.innerHTML = '';
|
||||
const now = new Date(j.at).getTime();
|
||||
if (!j.connections || j.connections.length === 0) {
|
||||
if (!j.tunnels || j.tunnels.length === 0) {
|
||||
empty.style.display = '';
|
||||
count.textContent = '';
|
||||
} else {
|
||||
empty.style.display = 'none';
|
||||
count.textContent = '(' + j.connections.length + ')';
|
||||
for (const c of j.connections) {
|
||||
count.textContent = '(' + j.tunnels.length + ')';
|
||||
for (const c of j.tunnels) {
|
||||
const opened = new Date(c.opened_at).getTime();
|
||||
const ageSecs = (now - opened) / 1000;
|
||||
const idleCls = c.idle_seconds > 60 ? 'dead' : c.idle_seconds > 30 ? 'stale' : '';
|
||||
@@ -118,7 +118,7 @@ async function refreshConnections() {
|
||||
rows.appendChild(tr);
|
||||
}
|
||||
}
|
||||
document.getElementById('meta').textContent = '— ' + j.connections.length + ' bridges';
|
||||
document.getElementById('meta').textContent = '— ' + j.tunnels.length + ' tunnels';
|
||||
} catch (e) {
|
||||
document.getElementById('meta').textContent = '— api error';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user