{"record":{"id":"a607fa4fc4a57849","repo":"MHSanaei/3x-ui","slug":"invalid-metric-unknown-metric","errorCode":null,"errorMessage":"invalid metric (unknown metric)","messagePattern":"invalid metric \\(unknown metric\\)","errorType":"http","errorClass":null,"httpStatus":200,"severity":"error","filePath":"internal/web/controller/server.go","lineNumber":157,"sourceCode":"\tif !slices.Contains(service.SystemMetricKeys, metric) {\n\t\tjsonMsg(c, \"invalid metric\", fmt.Errorf(\"unknown metric\"))\n\t\treturn\n\t}\n\tbucket, ok := parseHistoryBucket(c)\n\tif !ok {\n\t\treturn\n\t}\n\tjsonObj(c, a.serverService.AggregateSystemMetric(metric, bucket, 60), nil)\n}\n\nfunc (a *ServerController) getXrayMetricsState(c *gin.Context) {\n\tjsonObj(c, a.xrayMetricsService.State(), nil)\n}\n\nfunc (a *ServerController) getXrayMetricsHistoryBucket(c *gin.Context) {\n\tmetric := c.Param(\"metric\")\n\tif !slices.Contains(service.XrayMetricKeys, metric) {\n\t\tjsonMsg(c, \"invalid metric\", fmt.Errorf(\"unknown metric\"))\n\t\treturn\n\t}\n\tbucket, ok := parseHistoryBucket(c)\n\tif !ok {\n\t\treturn\n\t}\n\tjsonObj(c, a.xrayMetricsService.AggregateMetric(metric, bucket, 60), nil)\n}\n\nfunc (a *ServerController) getXrayObservatory(c *gin.Context) {\n\tjsonObj(c, a.xrayMetricsService.ObservatorySnapshot(), nil)\n}\n\nfunc (a *ServerController) getXrayObservatoryHistoryBucket(c *gin.Context) {\n\ttag := c.Param(\"tag\")\n\tif !a.xrayMetricsService.HasObservatoryTag(tag) {\n\t\tjsonMsg(c, \"invalid tag\", fmt.Errorf(\"unknown observatory tag\"))\n\t\treturn","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/controller/server.go#L139-L175","documentation":"ServerController.getXrayMetricsHistoryBucket validates its metric path parameter against service.XrayMetricKeys — a different allowlist from the system metrics — and returns 'invalid metric' (detail 'unknown metric') on mismatch. These keys describe Xray child-process counters exposed by the xray metrics service, so the accepted names depend on what that service tracks.","triggerScenarios":"GET /panel/api/server/history/xray/:metric/:bucket with a system-metric name ('cpu') or an invented Xray counter name instead of one of service.XrayMetricKeys.","commonSituations":"Confusing the system-metric endpoint's keys with the Xray-metric endpoint's keys; version change renaming Xray counters; scripts reused across endpoint families.","solutions":["Use only keys from service.XrayMetricKeys for this endpoint (check the var in your build).","Do not assume system metric names carry over — enumerate the correct list for /history/xray/.","After upgrading Xray-core or the panel, re-check the key set for renames."],"exampleFix":"# before\nGET /panel/api/server/history/xray/mem/60   # system key on xray endpoint -> invalid metric\n\n# after\nGET /panel/api/server/history/xray/<key from service.XrayMetricKeys>/60","handlingStrategy":"type-guard","validationCode":"// keep in sync with service.XrayMetricKeys in the deployed build\nconst XRAY_METRIC_KEYS = [...] as const // copy from source\nfunction isXrayMetric(m: string): m is (typeof XRAY_METRIC_KEYS)[number] {\n  return (XRAY_METRIC_KEYS as readonly string[]).includes(m)\n}","typeGuard":"function isXrayMetric(m: string): m is (typeof XRAY_METRIC_KEYS)[number] {\n  return (XRAY_METRIC_KEYS as readonly string[]).includes(m)\n}","tryCatchPattern":null,"preventionTips":["Never reuse system-metric key lists on the /history/xray/ endpoints.","Regenerate API client constants after upgrading the panel.","Pin dashboards to a known panel version or fetch key lists dynamically."],"tags":["api","http-400","validation","metrics","xray"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}