Files
llmgw/internal/web/fabric_page.go
T
Ilker ManapandClaude Opus 5.5 11231d8c2b The portal in English and Turkish: a catalogue per language, a switcher at the foot of every page, landing.language, Turkish numbers and dates
Every template and handler message in internal/web goes through a JSON
catalogue under internal/web/locales/<code>/, with English as the fallback.
The language is the person's choice (a cookie set by /_lang/{code}), else
Accept-Language, else landing.language. Tests refuse a key some language
lacks, a translation with other arguments than English, and template text
left in English.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 00:12:53 +02:00

128 lines
3.9 KiB
Go

package web
import (
"encoding/json"
"html/template"
"net/http"
"strconv"
"time"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
)
// TopologySource is a Source that can map its fabrics.
type TopologySource interface {
Topologies() []adminapi.TopologyView
Topology(id string) (adminapi.TopologyView, bool)
}
type fabricPageData struct {
Nav Nav
OnAdminListener bool
Base string
List []adminapi.TopologyView
Current *adminapi.TopologyView
// Data is the current fabric as JSON, for the drawing to start from
// without a round trip. json.Marshal escapes <, > and &, so a switch
// description cannot close the script it sits in.
Data template.JS
// Strings are what the drawing says, in the page's language, as JSON
// beside the data: the script is a static file and has no catalogue.
Strings template.JS
Version string
Now string
}
// fabricJSStrings are the drawing's sentences and how many arguments each
// takes. An argument is handed to the catalogue as "{0}", "{1}", which the
// script fills in, so a translation moves them with %[2]s as anywhere else.
var fabricJSStrings = []struct {
key string
args int
}{
{"map_failed", 1}, {"not_mapped", 0},
{"tier.leaf", 0}, {"tier.spine", 0}, {"tier.core", 0}, {"tier.supercore", 0}, {"tier.n", 1},
{"adapters_row", 0}, {"ports_used", 2}, {"hosts", 1}, {"cables_n", 1}, {"below_rate", 1},
{"adapters_n", 1}, {"degraded_n", 1}, {"rail", 1}, {"not_ours", 0},
{"dt.tier", 0}, {"dt.ports", 0}, {"dt.adapters", 0}, {"dt.adapter", 0}, {"dt.rail", 0},
{"dt.leaf", 0}, {"dt.state", 0}, {"dt.rate", 0}, {"dt.sending", 0}, {"dt.receiving", 0},
{"cabled", 2}, {"degraded_cables", 0}, {"all_good", 0}, {"port_to", 3}, {"close", 0},
{"open_node", 1}, {"adapter", 0},
{"stat.switches", 0}, {"stat.adapters", 0}, {"stat.cables", 0}, {"stat.degraded", 0},
{"stat.rails", 0}, {"stat.aligned", 0}, {"stat.crossed", 0}, {"stat.sent", 0},
}
func init() {
for _, s := range fabricJSStrings {
registerKeys("fabric.js." + s.key)
}
}
// fabricStrings is the drawing's sentences in l, with the decimal sign its
// figures are written with.
func fabricStrings(l *Lang) template.JS {
m := map[string]string{"decimal": l.decimal}
for _, s := range fabricJSStrings {
args := make([]any, s.args)
for i := range args {
args[i] = "{" + strconv.Itoa(i) + "}"
}
m[s.key] = l.T("fabric.js."+s.key, args...)
}
b, _ := json.Marshal(m)
return template.JS(b) //nolint:gosec // json.Marshal escapes <, > and &
}
// FabricsPage shows every physical fabric and, for one, its map.
func FabricsPage(src Source, onAdminListener bool, version string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ts, ok := src.(TopologySource)
if !ok {
http.NotFound(w, r)
return
}
d := fabricPageData{Nav: navFrom(r, "cluster"), OnAdminListener: onAdminListener, Base: "/admin/fabrics",
List: ts.Topologies(), Version: version, Now: time.Now().UTC().Format(time.RFC3339),
Strings: fabricStrings(langOf(r))}
if onAdminListener {
d.Base = "/fabrics"
}
id := r.PathValue("id")
if id == "" && len(d.List) > 0 {
id = d.List[0].ID
}
if id != "" {
t, ok := ts.Topology(id)
if !ok {
http.NotFound(w, r)
return
}
d.Current = &t
b, _ := json.Marshal(t)
d.Data = template.JS(b) //nolint:gosec // json.Marshal escapes <, > and &
}
noStore(w)
render(w, r, "fabrics", d)
}
}
// FabricTopologyJSON is one fabric's map and live state, for the drawing to
// refresh from.
func FabricTopologyJSON(src Source) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ts, ok := src.(TopologySource)
if !ok {
http.NotFound(w, r)
return
}
t, ok := ts.Topology(r.PathValue("id"))
if !ok {
http.NotFound(w, r)
return
}
noStore(w)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(t)
}
}