MHSanaei/3x-ui · error
invalid metric
Error message
invalid metric
What it means
ServerController.getMetricHistoryBucket serves per-tab system metric history and validates c.Param("metric") against service.SystemMetricKeys (cpu, mem, netUp, netDown, online, load1/5/15 per the handler comment). An unknown key returns 'invalid metric' / 'unknown metric' before the bucket is even parsed.
Source
Thrown at internal/web/controller/server.go:140
// getCpuHistoryBucket retrieves aggregated CPU usage history based on the specified time bucket.
// Kept for back-compat; new callers should use /history/cpu/:bucket which
// returns {"t","v"} (uniform across all metrics) instead of {"t","cpu"}.
func (a *ServerController) getCpuHistoryBucket(c *gin.Context) {
bucket, ok := parseHistoryBucket(c)
if !ok {
return
}
jsonObj(c, a.serverService.AggregateCpuHistory(bucket, 60), nil)
}
// getMetricHistoryBucket returns up to 60 buckets of history for a single
// system metric (cpu, mem, netUp, netDown, online, load1/5/15). The
// SystemHistoryModal calls one endpoint per active tab.
func (a *ServerController) getMetricHistoryBucket(c *gin.Context) {
metric := c.Param("metric")
if !slices.Contains(service.SystemMetricKeys, metric) {
jsonMsg(c, "invalid metric", fmt.Errorf("unknown metric"))
return
}
bucket, ok := parseHistoryBucket(c)
if !ok {
return
}
jsonObj(c, a.serverService.AggregateSystemMetric(metric, bucket, 60), nil)
}
func (a *ServerController) getXrayMetricsState(c *gin.Context) {
jsonObj(c, a.xrayMetricsService.State(), nil)
}
func (a *ServerController) getXrayMetricsHistoryBucket(c *gin.Context) {
metric := c.Param("metric")
if !slices.Contains(service.XrayMetricKeys, metric) {
jsonMsg(c, "invalid metric", fmt.Errorf("unknown metric"))
returnView on GitHub (pinned to ad32144c42)
Solutions
- Use exactly one of: cpu, mem, netUp, netDown, online, load1, load5, load15 (confirm via service.SystemMetricKeys in your build).
- Match casing — netUp not net_up.
- Upgrade frontend and backend together so the SystemHistoryModal tabs align with the allowlist.
Example fix
# before GET /panel/api/server/history/metric/memory/60 # invalid metric # after GET /panel/api/server/history/metric/mem/60
Defensive patterns
Strategy: type-guard
Validate before calling
const SYSTEM_METRIC_KEYS = ['cpu','mem','netUp','netDown','online','load1','load5','load15'] as const
function isSystemMetric(m: string): m is (typeof SYSTEM_METRIC_KEYS)[number] {
return (SYSTEM_METRIC_KEYS as readonly string[]).includes(m)
} Type guard
function isSystemMetric(m: string): m is (typeof SYSTEM_METRIC_KEYS)[number] {
return (SYSTEM_METRIC_KEYS as readonly string[]).includes(m)
} Prevention
- Use the exact camelCase keys; do not snake_case them.
- Keep the metric list in a generated/shared module so renaming is caught at compile time.
- Distinguish system-metric keys from xray-metric keys per endpoint.
When it happens
Trigger: GET /panel/api/server/history/metric/:metric/:bucket with e.g. 'memory' (key is 'mem'), 'net_up' (key is 'netUp'), 'load' (must be load1/load5/load15), or any invented key.
Common situations: Guessing snake_case names when the API uses camelCase; frontend/backend drift after partial upgrade; copying metric names from the node endpoint when the sets differ.
Related errors
- invalid metric
- invalid bucket
- invalid bucket
- invalid metric (unknown metric)
- invalid tag (unknown observatory tag)
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/540b8dbfba7646ac.
Report an issue: GitHub.