hub: S2 /offsite registry page (read-only) + nav + WGPeer.CreatedAt
Endpoint card + peers table (truncated pubkeys with full-value title attr, bound peers link to /hosts/<id>); Offsite nav link in all 9 page templates; render tests for endpoint/peers, empty, and not-configured states. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
package web
|
||||
|
||||
// S2 offsite connectivity: the read-only /offsite registry page (doc 06 §8 S2 "hub UI shows the
|
||||
// peer registry"). Read-only by design — add/remove stay on the admin API; UI mutations arrive
|
||||
// with tunnel health (S3/S6). Peers render neutral (no health state exists yet — nothing to
|
||||
// except on, so no status colors).
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// truncateMiddle shortens a long opaque value (pubkeys) for table display, keeping both ends —
|
||||
// the full value always rides the title attribute.
|
||||
func truncateMiddle(s string, keep int) string {
|
||||
if len(s) <= 2*keep+1 {
|
||||
return s
|
||||
}
|
||||
return s[:keep] + "…" + s[len(s)-keep:]
|
||||
}
|
||||
|
||||
// offsitePeerRow is the per-peer view model.
|
||||
type offsitePeerRow struct {
|
||||
Pubkey string
|
||||
PubkeyShort string
|
||||
AssignedIP string // with /32
|
||||
HostID string // "" = unbound
|
||||
Note string
|
||||
CreatedAt string
|
||||
}
|
||||
|
||||
// handleOffsite renders the offsite registry: the endpoint card + the peer table.
|
||||
func (s *Server) handleOffsite(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"HasEndpoint": false,
|
||||
}
|
||||
|
||||
ep, err := s.store.GetWGEndpoint()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
s.logger.Printf("[ERROR] offsite: endpoint read: %v", err)
|
||||
http.Error(w, "Internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if ep != nil {
|
||||
data["HasEndpoint"] = true
|
||||
data["Endpoint"] = map[string]interface{}{
|
||||
"DNSName": ep.DNSName,
|
||||
"WGPort": ep.WGPort,
|
||||
"ServerPubkey": ep.ServerPubkey,
|
||||
"ServerPubkeyShort": truncateMiddle(ep.ServerPubkey, 10),
|
||||
"TunnelSubnet": ep.TunnelSubnet,
|
||||
"PBSTunnelIP": ep.PBSTunnelIP,
|
||||
}
|
||||
}
|
||||
|
||||
peers, err := s.store.ListWGPeers()
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] offsite: peer list: %v", err)
|
||||
http.Error(w, "Internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rows := make([]offsitePeerRow, 0, len(peers))
|
||||
for _, p := range peers {
|
||||
rows = append(rows, offsitePeerRow{
|
||||
Pubkey: p.Pubkey,
|
||||
PubkeyShort: truncateMiddle(p.Pubkey, 10),
|
||||
AssignedIP: p.AssignedIP + "/32",
|
||||
HostID: p.HostID,
|
||||
Note: p.Note,
|
||||
CreatedAt: p.CreatedAt,
|
||||
})
|
||||
}
|
||||
data["Peers"] = rows
|
||||
|
||||
if err := s.templates.ExecuteTemplate(w, "offsite.html", data); err != nil {
|
||||
s.logger.Printf("[ERROR] offsite.html template: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user