Add 1d/7d/all time-range filter to admin by-model stats
build / build (push) Successful in 2m32s

By-model panel gains a segmented 1天/7天/全部 control. Selecting a
range fetches /api/stats?since=<RFC3339> and re-renders only that table
client-side, replicating the human/rate template helpers in JS; the
existing /api/stats since param already drives the server-side filter.

- internal/server/templates/admin.html: filter UI + JS, empty/loading states
- internal/server/server_test.go: render test for the filter controls
- scripts/test-instance.sh: build→start→stop→clean test-instance helper
This commit is contained in:
2026-08-20 17:08:39 +08:00
parent 9aba511abe
commit a0a2049440
3 changed files with 230 additions and 5 deletions
+42
View File
@@ -15,6 +15,7 @@ import (
"git.misaka.ren/M1saka/zhanlu_proxy/internal/auth"
"git.misaka.ren/M1saka/zhanlu_proxy/internal/config"
"git.misaka.ren/M1saka/zhanlu_proxy/internal/stats"
"git.misaka.ren/M1saka/zhanlu_proxy/internal/store"
)
@@ -365,6 +366,47 @@ func TestModelTestNoCredentials(t *testing.T) {
}
}
// TestAdminRendersModelRangeFilter verifies the admin page renders without
// panic for both empty and populated summaries, and that the by-model panel
// carries the 1d/7d/all range filter controls and a client-renderable tbody.
func TestAdminRendersModelRangeFilter(t *testing.T) {
populated := &stats.Summary{
PerModel: []stats.ModelStat{
{Model: "GLM-4.7", Requests: 3, PromptTokens: 10, CompletionTokens: 20, TotalTokens: 30, CachedTokens: 4},
},
}
cases := []struct {
name string
summary *stats.Summary
}{
{"empty", &stats.Summary{}},
{"populated", populated},
}
for _, tc := range cases {
var buf bytes.Buffer
if err := adminTemplate.Execute(&buf, map[string]any{
"Enabled": true,
"Stats": tc.summary,
"DBPath": "zhanlu.db",
"PasswordEnabled": false,
}); err != nil {
t.Fatalf("%s: render admin: %v", tc.name, err)
}
body := buf.String()
for _, want := range []string{
`id="model-range"`,
`data-range="1d"`,
`data-range="7d"`,
`data-range="all"`,
`id="model-tbody"`,
} {
if !strings.Contains(body, want) {
t.Fatalf("%s: admin output missing %q", tc.name, want)
}
}
}
}
// totNum extracts an int from a JSON-decoded numeric value (float64).
func totNum(v any) int {
f, _ := v.(float64)