{"record":{"id":"ad18bb62d313ec9f","repo":"glanceapp/glance","slug":"service-must-be-one-of-s-s-s-s","errorCode":null,"errorMessage":"service must be one of: %s, %s, %s, %s","messagePattern":"service must be one of: (.+?), (.+?), (.+?), (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/glance/widget-dns-stats.go","lineNumber":81,"sourceCode":"func (widget *dnsStatsWidget) initialize() error {\n\ttitleURL := strings.TrimRight(widget.URL, \"/\")\n\tswitch widget.Service {\n\tcase dnsServicePihole, dnsServicePiholeV6:\n\t\ttitleURL = titleURL + \"/admin\"\n\t}\n\n\twidget.\n\t\twithTitle(\"DNS Stats\").\n\t\twithTitleURL(titleURL).\n\t\twithCacheDuration(10 * time.Minute)\n\n\tswitch widget.Service {\n\tcase dnsServiceAdguard:\n\tcase dnsServicePiholeV6:\n\tcase dnsServicePihole:\n\tcase dnsServiceTechnitium:\n\tdefault:\n\t\treturn fmt.Errorf(\"service must be one of: %s, %s, %s, %s\", dnsServiceAdguard, dnsServicePihole, dnsServicePiholeV6, dnsServiceTechnitium)\n\t}\n\n\treturn nil\n}\n\nfunc (widget *dnsStatsWidget) update(ctx context.Context) {\n\tvar stats *dnsStats\n\tvar err error\n\n\tswitch widget.Service {\n\tcase dnsServiceAdguard:\n\t\tstats, err = fetchAdguardStats(widget.URL, widget.AllowInsecure, widget.Username, widget.Password, widget.HideGraph)\n\tcase dnsServicePihole:\n\t\tstats, err = fetchPihole5Stats(widget.URL, widget.AllowInsecure, widget.Token, widget.HideGraph)\n\tcase dnsServiceTechnitium:\n\t\tstats, err = fetchTechnitiumStats(widget.URL, widget.AllowInsecure, widget.Token, widget.HideGraph)\n\tcase dnsServicePiholeV6:\n\t\tvar newSessionID string","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/widget-dns-stats.go#L63-L99","documentation":"Returned by dnsStatsWidget.Initialize when widget.Service is not one of the four supported constants: adguard, pihole_v6 (dnsServicePiholeV6), pihole, technitium. It is a pure config-validation error raised at page load before any network activity.","triggerScenarios":"Setting service: adguard-home, service: PiHole, service: nextdns, or omitting/changing the key in a dns-stats widget block.","commonSituations":"Guessing the service name instead of copying the docs; case mismatch; renaming after upgrading glance versions that changed accepted values.","solutions":["Set service to exactly one of: adguard, pihole, pihole_v6, technitium (lowercase).","Check YAML indentation — service: must sit under the dns-stats widget, not a sibling key.","After editing, reload the page/config; the error appears immediately on render."],"exampleFix":"# before\n- type: dns-stats\n  service: adguard-home\n\n# after\n- type: dns-stats\n  service: adguard","handlingStrategy":"validation","validationCode":"var validDNSServices = map[string]bool{\n    \"adguard\": true, \"pihole\": true, \"pihole_v6\": true, \"technitium\": true,\n}\nif !validDNSServices[widget.Service] {\n    return fmt.Errorf(\"dns-stats: service must be one of adguard, pihole, pihole_v6, technitium, got %q\", widget.Service)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Copy service values verbatim from the glance dns-stats docs.","Use lowercase identifiers; no spaces or capitalization.","Add a config-lint step that checks enumerated fields against allowlists."],"tags":["config","validation","dns-stats","widget"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}