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>
144 lines
4.1 KiB
Go
144 lines
4.1 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
|
|
)
|
|
|
|
// CapacityAdmin sets what parts of the tenancy tree are guaranteed of the
|
|
// cluster and may hold. The admin API implements it; the page and the CLI go
|
|
// through the same checks.
|
|
type CapacityAdmin interface {
|
|
QuotaViews(ctx context.Context) ([]adminapi.QuotaView, error)
|
|
SetQuota(ctx context.Context, req adminapi.QuotaRequest, by string) (string, error)
|
|
ClearQuota(ctx context.Context, scope string) (string, error)
|
|
Scopes(ctx context.Context) ([]adminapi.ScopeName, error)
|
|
}
|
|
|
|
// SetCapacity gives the quotas page capacity quotas to show and set. Without
|
|
// it the section is left off.
|
|
func (p *AccountPages) SetCapacity(c CapacityAdmin) { p.capacity = c }
|
|
|
|
// capacityRow is one scope's capacity quota as the page shows it.
|
|
type capacityRow struct {
|
|
adminapi.QuotaView
|
|
GPUs, Cores, Memory string
|
|
}
|
|
|
|
// capacityForm is what the form starts with: a scope's quota to change, or
|
|
// nothing for a new one.
|
|
type capacityForm struct {
|
|
Scope string
|
|
GPUs, Cores, Memory string
|
|
LGPUs, LCores, LMem string
|
|
}
|
|
|
|
func (p *AccountPages) capacitySection(ctx context.Context, l *Lang, d *adminQuotasData, editing string) {
|
|
if p.capacity == nil {
|
|
return
|
|
}
|
|
d.HasCapacity = true
|
|
views, err := p.capacity.QuotaViews(ctx)
|
|
if err != nil {
|
|
d.Error = l.Err(err)
|
|
return
|
|
}
|
|
for _, v := range views {
|
|
d.Capacity = append(d.Capacity, capacityRow{QuotaView: v,
|
|
GPUs: l.capCell(v.Holds.GPUs, v.Guarantee.GPUs, v.Limit.GPUs),
|
|
Cores: l.capCell(v.Holds.CPUCores, v.Guarantee.CPUCores, v.Limit.CPUCores),
|
|
Memory: l.capCell(v.Holds.MemoryGB, v.Guarantee.MemoryGB, v.Limit.MemoryGB)})
|
|
if v.Scope == editing {
|
|
d.CapForm = capacityForm{Scope: v.Scope,
|
|
GPUs: intStr(v.Guarantee.GPUs), Cores: intStr(v.Guarantee.CPUCores), Memory: intStr(v.Guarantee.MemoryGB),
|
|
LGPUs: intStr(v.Limit.GPUs), LCores: intStr(v.Limit.CPUCores), LMem: intStr(v.Limit.MemoryGB)}
|
|
}
|
|
}
|
|
if d.Scopes, err = p.capacity.Scopes(ctx); err != nil {
|
|
d.Error = l.Err(err)
|
|
}
|
|
}
|
|
|
|
// capCell is a capacity figure in English, for a caller with no request to
|
|
// take a language from.
|
|
func capCell(holds int, g, l *int) string { return english.capCell(holds, g, l) }
|
|
|
|
// capCell is "6 of 8 (at most 16)": held, guaranteed, limit.
|
|
func (lang *Lang) capCell(holds int, g, l *int) string {
|
|
if g == nil && l == nil {
|
|
return ""
|
|
}
|
|
out := lang.Int(int64(holds))
|
|
if g != nil {
|
|
out = lang.T("capacity.cell.of", out, lang.Int(int64(*g)))
|
|
}
|
|
if l != nil {
|
|
out = lang.T("capacity.cell.at_most", out, lang.Int(int64(*l)))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func intStr(v *int) string {
|
|
if v == nil {
|
|
return ""
|
|
}
|
|
return strconv.Itoa(*v)
|
|
}
|
|
|
|
// applyCapacity sets or clears one scope's capacity quota from the form, and
|
|
// says what it did in the request's language. What the admin API refuses is
|
|
// its own English.
|
|
func (p *AccountPages) applyCapacity(r *http.Request, admin store.User) (string, error) {
|
|
l := langOf(r)
|
|
scope := strings.TrimSpace(r.FormValue("scope"))
|
|
if scope == "" {
|
|
return "", quotaRefusal{key: "capacity.err.scope"}
|
|
}
|
|
if r.FormValue("clear") != "" {
|
|
key, err := p.capacity.ClearQuota(r.Context(), scope)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return l.T("capacity.msg.removed", key), nil
|
|
}
|
|
field := func(name string) (*int, error) {
|
|
v := strings.TrimSpace(r.FormValue(name))
|
|
if v == "" {
|
|
return nil, nil
|
|
}
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil || n < 0 {
|
|
return nil, quotaRefusal{key: "capacity.err." + name}
|
|
}
|
|
return &n, nil
|
|
}
|
|
var req adminapi.QuotaRequest
|
|
req.Scope = scope
|
|
var err error
|
|
for _, f := range []struct {
|
|
dst **int
|
|
name string
|
|
}{
|
|
{&req.Guarantee.GPUs, "gpus"},
|
|
{&req.Guarantee.CPUCores, "cores"},
|
|
{&req.Guarantee.MemoryGB, "memory"},
|
|
{&req.Limit.GPUs, "limit_gpus"},
|
|
{&req.Limit.CPUCores, "limit_cores"},
|
|
{&req.Limit.MemoryGB, "limit_memory"},
|
|
} {
|
|
if *f.dst, err = field(f.name); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
key, err := p.capacity.SetQuota(r.Context(), req, admin.Email)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return l.T("capacity.msg.set", key), nil
|
|
}
|