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>
108 lines
4.8 KiB
HTML
108 lines
4.8 KiB
HTML
{{define "org"}}<!doctype html>
|
|
<html lang="{{lang}}"><head>
|
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>{{.Org.Name}} · llmgw</title><link rel="stylesheet" href="{{stylesheet}}">
|
|
</head><body><div class="wrap">
|
|
<header>
|
|
<h1>{{.Org.Name}}</h1>
|
|
<p class="sub">{{tn "org.sub" .People}}</p>
|
|
{{template "nav" .Nav}}
|
|
</header>
|
|
|
|
{{if gt (len .Orgs) 1}}
|
|
<div class="filters">
|
|
<span class="filter-label">{{t "org.filter"}}</span>
|
|
{{range .Orgs}}<a class="chip{{if eq .ID $.Org.ID}} on{{end}}" href="/org/{{.ID}}">{{.Name}}</a>{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
|
|
|
|
<h2>{{t "org.use"}}</h2>
|
|
<div class="filters">
|
|
<span class="filter-label">{{t "usage.group_by"}}</span>
|
|
{{range .Groups}}<a class="chip{{if .Selected}} on{{end}}"
|
|
href="/org/{{$.Org.ID}}?by={{.Key}}&window={{$.Window}}">{{.Label}}</a>{{end}}
|
|
<span class="filter-label">{{t "usage.window"}}</span>
|
|
{{range .Windows}}<a class="chip{{if .Selected}} on{{end}}"
|
|
href="/org/{{$.Org.ID}}?by={{$.By}}&window={{.Key}}">{{.Label}}</a>{{end}}
|
|
</div>
|
|
|
|
<div class="chart-row">
|
|
{{template "tschart" dict "t" .Requests "title" (t "usage.requests")
|
|
"note" (t "org.requests_note")}}
|
|
{{template "tschart" dict "t" .Tokens "title" (t "usage.tokens")
|
|
"note" (t "org.tokens_note")}}
|
|
</div>
|
|
{{if .Priced}}
|
|
<div class="chart-row">
|
|
{{template "tschart" dict "t" .Spend "title" (t "usage.spend") "note" (t "org.spend_note")}}
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .Rows}}
|
|
<table class="rows compact">
|
|
<tr><th>{{if eq .By "user"}}{{t "org.col.person"}}{{else if eq .By "project"}}{{t "org.col.project"}}{{else}}{{t "org.col.team"}}{{end}}</th>
|
|
<th class="num">{{t "usage.requests"}}</th><th class="num">{{t "usage.tokens"}}</th><th class="num">{{t "usage.spend"}}</th><th class="num">{{t "usage.errors"}}</th></tr>
|
|
{{range .Rows}}<tr>
|
|
<td>{{.Name}}</td><td class="num">{{compact .Requests}}</td><td class="num">{{compact .Tokens}}</td>
|
|
<td class="num">{{.Spend}}</td><td class="num">{{if .Errors}}{{count .Errors}}{{else}}—{{end}}</td>
|
|
</tr>{{end}}
|
|
</table>
|
|
{{else}}<p class="empty">{{t "org.use_empty"}}</p>{{end}}
|
|
<p class="note">{{t "org.use_scope"}}</p>
|
|
|
|
<h2>{{t "org.limits.title"}}</h2>
|
|
{{if .Quotas}}
|
|
<table class="rows quota-tree">
|
|
<tr><th>{{t "quotas.col.where"}}</th><th class="num">{{t "quotas.col.rpm"}}</th><th class="num">{{t "quotas.col.tpm"}}</th>
|
|
<th class="num">{{t "quotas.col.budget"}}</th><th>{{t "org.col.spent_30d"}}</th></tr>
|
|
{{range .Quotas}}
|
|
<tr class="q-depth-{{.Indent}}">
|
|
<td class="q-where"><span class="q-name">{{.Path}}</span> <span class="q-kind">{{t (print "quotas.kind." .Scope)}}</span>
|
|
{{if .Models}}<div class="q-sub">{{th "quotas.only_models" .Models}}</div>{{end}}
|
|
{{if .Shadowed}}<div class="q-sub q-warn">{{.Shadowed}}</div>{{end}}</td>
|
|
<td class="num">{{template "quota-value" dict "Own" .Shown.RPM "Up" .RPMUp "From" .RPMFrom}}</td>
|
|
<td class="num">{{template "quota-value" dict "Own" .Shown.TPM "Up" .TPMUp "From" .TPMFrom}}</td>
|
|
<td class="num">{{if .Budget}}{{.Shown.Budget}}{{if .Period}}<span class="q-per">/{{.Shown.Period}}</span>{{end}}
|
|
{{else}}{{template "quota-value" dict "Up" .BudgetUp "From" .BudgetFrom}}{{end}}</td>
|
|
<td class="q-spent"><span class="q-spend">{{.Spend}}</span>
|
|
{{if .HasPct}}<span class="q-meter{{if ge .Percent 90}} hot{{end}}"><i style="width:{{pct .Percent 100}}%"></i></span>
|
|
<span class="q-pct">{{t "quotas.pct" .Percent}}</span>{{end}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</table>
|
|
<p class="note">{{t "org.limits.note"}}</p>
|
|
{{end}}
|
|
|
|
<h2>{{t "org.members.title"}}</h2>
|
|
{{if .Members}}
|
|
<table class="rows compact">
|
|
<tr><th>{{t "org.col.person"}}</th><th>{{t "org.col.member_of"}}</th><th>{{t "org.col.state"}}</th></tr>
|
|
{{range .Members}}<tr>
|
|
<td>{{.Email}}</td>
|
|
<td>{{range .Places}}<span class="chip-m">{{.Where}}{{if eq .Role "manager"}} <b class="chip-role">{{t "org.role.manager"}}</b>{{end}}</span>{{end}}</td>
|
|
<td class="state state-{{.State}}">{{with .State}}{{t (print "org.state." .)}}{{end}}</td>
|
|
</tr>{{end}}
|
|
</table>
|
|
{{else}}<p class="empty">{{t "org.members.empty"}}</p>{{end}}
|
|
|
|
<footer><span>llmgw {{.Version}}</span><span>{{.Now}}</span>{{template "langs"}}</footer>
|
|
</div></body></html>{{end}}
|
|
|
|
|
|
{{define "org-none"}}<!doctype html>
|
|
<html lang="{{lang}}"><head>
|
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>{{t "org.none.title"}} · llmgw</title><link rel="stylesheet" href="{{stylesheet}}">
|
|
</head><body><div class="wrap">
|
|
<header>
|
|
<h1>{{t "org.none.title"}}</h1>
|
|
<p class="sub">{{t "org.none.sub"}}</p>
|
|
{{template "nav" .Nav}}
|
|
</header>
|
|
<p>{{t "org.none.about"}}</p>
|
|
<p>{{th "org.none.how"}}</p>
|
|
<footer><span>llmgw {{.Version}}</span><span>{{.Now}}</span>{{template "langs"}}</footer>
|
|
</div></body></html>{{end}}
|