package web
import (
"context"
"html/template"
"log/slog"
"net/http"
"slices"
"strconv"
"time"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/fabrics"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/nvlink"
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
)
// DashboardData is the whole page.
type DashboardData struct {
// CPU is the fleet's capacity when it has no GPUs, which is shown instead
// of a GPU table that would only say there are none.
CPU cpuCapacity
// Menu is the shared top menu; Nav below is what the cluster page adds.
Menu Nav
// GPUMap is every card coloured by one figure, and IdleGPUs the replicas
// holding cards that are doing nothing; both from the last heartbeats.
GPUMap *gpuMap
IdleGPUs []idleHolding
// Efficiency is what each model gets from its cards, from the metrics
// store; nil without one.
Efficiency []modelEfficiency
// Notice and Problem are what the last change made from this page did,
// carried over the redirect back to it.
Notice, Problem string
Nodes []adminapi.NodeView
Models []adminapi.ModelView
Replicas []adminapi.ReplicaView
Unplaced []adminapi.ExplainView
Usage []store.KeyUsage
Summary adminapi.Summary
// Fabrics is the physical fabrics and what disagrees about them, from a
// source that can say.
Fabrics *fabrics.Report
// NVLink is the multi-node NVLink domains, from a source that can say.
NVLink *nvlink.Report
Users []store.UserUsage
Models2 []store.ModelUsage
// Tiles are the headline figures, which are numbers rather than charts.
Requests24h int64
Tokens24h int64
Errors24h int64
Spend24h int64
RequestSpark template.HTML
TokenSpark template.HTML
HasPeople bool
// NodePrefix is what every node's name starts with, left off in tables
// so a row fits on a line; the full name is on hover.
NodePrefix string
// Columns shown only when some row has something in them: a CPU-only
// cluster's nodes table was three columns of dashes wide.
AnyGPUs, AnyClasses, AnyFabrics, AnyFallbacks bool
// Charts are pre-rendered rather than assembled in the template: building
// SVG out of template fragments is how a chart ends up unescaped.
RequestChart template.HTML
TokenChart template.HTML
LatencyChart template.HTML
HasCharts bool
NodeCount int
ModelCount int
ReadyReplicas int
TotalReplicas int
AuthEnabled bool
// OnAdminListener decides which links the page offers.
//
// The same page is served twice, and the two have different neighbours:
// metrics, pprof and the management API exist only on the admin listener,
// while account administration and the profile exist only on the
// client-facing one. A nav that assumes one of them sends every visitor to
// the other into a 404 — or, on the public port, into "no api key was
// provided", which is a far more confusing way to say the same thing.
OnAdminListener bool
// Fleet is the operational charts, nil when no metrics store is configured.
Fleet *FleetCharts
// Nav says which optional admin pages are mounted. Asked rather than
// assumed: a link to a page that was never registered lands on whatever the
// catch-all route serves, which on the client-facing port is the landing
// page — an admin clicking "mirror" and getting the marketing copy has no
// way to tell that from a bug.
Nav NavExtras
RefreshSec int
// RefreshMeta is the reload for a browser without script.
RefreshMeta template.HTML
Version string
Now string
}
// stateWords are every state a node, a replica, a shard or an engine's
// installation can be in. Each is a CSS class and a word; the class stays as
// it is and the word is looked up, as dash.state.. A state not listed
// here shows its key, which the catalogue test is there to stop.
var stateWords = []string{
"ready", "cordoned", "draining", "unreachable", "lost", "unknown",
"placing", "pending", "starting", "loading", "stopped", "failed",
"installing", "unsupported", "skipped",
}
func init() {
for _, s := range stateWords {
registerKeys("dash.state." + s)
}
// How bad a fabric's or an NVLink domain's problem is.
registerKeys("fabric.level.fail", "fabric.level.warn", "fabric.level.info")
}
// refreshMeta is the meta element that reloads a page every sec seconds,
// for the