{"record":{"id":"ba8b26f3c20fadf0","repo":"MHSanaei/3x-ui","slug":"invalid-bucket-ba8b26","errorCode":null,"errorMessage":"invalid bucket","messagePattern":"invalid bucket","errorType":"http","errorClass":null,"httpStatus":200,"severity":"error","filePath":"internal/web/controller/server.go","lineNumber":117,"sourceCode":"\t})\n\t_, _ = c.AddFunc(\"@every 1m\", func() {\n\t\tif err := service.PersistSystemMetrics(); err != nil {\n\t\t\tlogger.Warning(\"persist system metrics failed:\", err)\n\t\t}\n\t})\n}\n\n// status returns the current server status information.\nfunc (a *ServerController) status(c *gin.Context) { jsonObj(c, a.serverService.LastStatus(), nil) }\n\nfunc (a *ServerController) getFail2banStatus(c *gin.Context) {\n\tjsonObj(c, a.serverService.GetFail2banStatus(), nil)\n}\n\nfunc parseHistoryBucket(c *gin.Context) (int, bool) {\n\tbucket, err := strconv.Atoi(c.Param(\"bucket\"))\n\tif err != nil || bucket <= 0 || !service.IsAllowedHistoryBucket(bucket) {\n\t\tjsonMsg(c, \"invalid bucket\", fmt.Errorf(\"unsupported bucket\"))\n\t\treturn 0, false\n\t}\n\treturn bucket, true\n}\n\n// getCpuHistoryBucket retrieves aggregated CPU usage history based on the specified time bucket.\n// Kept for back-compat; new callers should use /history/cpu/:bucket which\n// returns {\"t\",\"v\"} (uniform across all metrics) instead of {\"t\",\"cpu\"}.\nfunc (a *ServerController) getCpuHistoryBucket(c *gin.Context) {\n\tbucket, ok := parseHistoryBucket(c)\n\tif !ok {\n\t\treturn\n\t}\n\tjsonObj(c, a.serverService.AggregateCpuHistory(bucket, 60), nil)\n}\n\n// getMetricHistoryBucket returns up to 60 buckets of history for a single\n// system metric (cpu, mem, netUp, netDown, online, load1/5/15). The","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/controller/server.go#L99-L135","documentation":"parseHistoryBucket in ServerController is the shared guard for all /server/history endpoints (CPU, system metrics, Xray metrics, observatory). It Atoi-parses c.Param(\"bucket\"), requires >0, and requires service.IsAllowedHistoryBucket to accept it; failure writes 'invalid bucket' / 'unsupported bucket' and signals the handler to return. Same semantics as the node-side guard but for server-wide history.","triggerScenarios":"GET /panel/api/server/history/cpu/:bucket (or the metric/xray-metric/observatory variants) with bucket=0, negative, non-integer, or an integer not in the allowed set.","commonSituations":"Bookmark or dashboard URL from an older version using a bucket size the new build dropped; typo in the path; scripts reusing a node-side bucket that differs from the server-side set.","solutions":["Call with an allowed integer bucket (verify against service.IsAllowedHistoryBucket for your build).","Drop stale bookmarks/dashboard URLs after upgrading; refetch bucket options from the UI.","Log the parsed bucket in your client when a 400 comes back to spot unit confusion (minutes vs seconds)."],"exampleFix":"# before\nGET /panel/api/server/history/cpu/1440   # guessed value, not allowed\n\n# after\nGET /panel/api/server/history/cpu/3600","handlingStrategy":"validation","validationCode":"if !service.IsAllowedHistoryBucket(bucket) {\n    return fmt.Errorf(\"bucket %d not allowed; use one of %v\", bucket, service.AllowedHistoryBuckets)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Centralize bucket choices in one constant list shared by frontend and backend docs.","Validate dashboard-configured bucket values against the current build after upgrades.","Treat this 400 as permanent — do not retry with the same value."],"tags":["api","http-400","validation","metrics"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}