generated from mathias/template-go-web
feat(atlas): Phase B tail — live namespaces + trim inline fallback
NamespaceSummary (test-first) renders the substrate ns line from live cluster namespaces (system-filtered, sorted, capped). Handler now overlays both node specs and the ns line via a single cached liveOverlay(). Removed the inline SUBSTRATE/STAGES/NS data — /api/atlas.json is the single source; the page shows an error banner on fetch failure instead of stale data. GPU model naming intentionally NOT done: koala's node has no GPU product label (only nvidia.com/gpu count), so "1× GPU" is the API's truth — naming the RTX 5070 would need GPU-feature-discovery, out of scope. Verified: build/vet/lint(0)/test green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+36
-24
@@ -37,8 +37,12 @@ func NewHandler() http.Handler {
|
||||
http.Error(w, "atlas build failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if live := liveSubstrate(); len(live) > 0 {
|
||||
a.Substrate = atlas.MergeSubstrate(a.Substrate, live)
|
||||
ld := liveOverlay()
|
||||
if len(ld.hosts) > 0 {
|
||||
a.Substrate = atlas.MergeSubstrate(a.Substrate, ld.hosts)
|
||||
}
|
||||
if ld.ns != "" {
|
||||
a.NS = "Tailscale mesh · " + ld.ns
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_ = json.NewEncoder(w).Encode(a)
|
||||
@@ -46,33 +50,41 @@ func NewHandler() http.Handler {
|
||||
return mux
|
||||
}
|
||||
|
||||
// substrate cache: query the cluster at most once per TTL; fall back to the
|
||||
// authored substrate (returns nil) whenever the cluster is unreachable.
|
||||
// liveOverlay holds cluster-sourced substrate facts (node specs + namespace line).
|
||||
type liveOverlayData struct {
|
||||
hosts []atlas.Host
|
||||
ns string
|
||||
}
|
||||
|
||||
// live cache: query the cluster at most once per TTL; fall back to the authored
|
||||
// substrate/ns (zero values) whenever the cluster is unreachable.
|
||||
var (
|
||||
subMu sync.Mutex
|
||||
subCache []atlas.Host
|
||||
subAt time.Time
|
||||
liveMu sync.Mutex
|
||||
liveCache liveOverlayData
|
||||
liveAt time.Time
|
||||
)
|
||||
|
||||
const substrateTTL = 30 * time.Second
|
||||
const liveTTL = 30 * time.Second
|
||||
|
||||
func liveSubstrate() []atlas.Host {
|
||||
subMu.Lock()
|
||||
defer subMu.Unlock()
|
||||
if !subAt.IsZero() && time.Since(subAt) < substrateTTL {
|
||||
return subCache
|
||||
func liveOverlay() liveOverlayData {
|
||||
liveMu.Lock()
|
||||
defer liveMu.Unlock()
|
||||
if !liveAt.IsZero() && time.Since(liveAt) < liveTTL {
|
||||
return liveCache
|
||||
}
|
||||
subAt = time.Now()
|
||||
raw, err := cluster.Nodes()
|
||||
if err != nil {
|
||||
subCache = nil
|
||||
return nil
|
||||
liveAt = time.Now()
|
||||
|
||||
var d liveOverlayData
|
||||
if raw, err := cluster.Nodes(); err == nil {
|
||||
if hosts, err := atlas.HostsFromNodes(raw); err == nil {
|
||||
d.hosts = hosts
|
||||
}
|
||||
}
|
||||
hosts, err := atlas.HostsFromNodes(raw)
|
||||
if err != nil {
|
||||
subCache = nil
|
||||
return nil
|
||||
if raw, err := cluster.Namespaces(); err == nil {
|
||||
if s, err := atlas.NamespaceSummary(raw); err == nil {
|
||||
d.ns = s
|
||||
}
|
||||
}
|
||||
subCache = hosts
|
||||
return hosts
|
||||
liveCache = d
|
||||
return d
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user