{"record":{"id":"47dc5011073635d7","repo":"gethomepage/homepage","slug":"missing-glances-url","errorCode":null,"errorMessage":"Missing Glances URL","messagePattern":"Missing Glances URL","errorType":"exception","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"src/pages/api/widgets/glances.js","lineNumber":14,"sourceCode":"import { getPrivateWidgetOptions } from \"utils/config/widget-helpers\";\nimport createLogger from \"utils/logger\";\nimport { parseVersionForUrl } from \"utils/proxy/api-helpers\";\nimport { httpProxy } from \"utils/proxy/http\";\n\nconst logger = createLogger(\"glances\");\n\nasync function retrieveFromGlancesAPI(privateWidgetOptions, endpoint) {\n  let errorMessage;\n  const url = privateWidgetOptions?.url;\n  if (!url) {\n    errorMessage = \"Missing Glances URL\";\n    logger.error(errorMessage);\n    throw new Error(errorMessage);\n  }\n\n  const apiUrl = `${url}/api/${privateWidgetOptions.version}/${endpoint}`;\n  const headers = {\n    \"Accept-Encoding\": \"application/json\",\n  };\n  if (privateWidgetOptions.username && privateWidgetOptions.password) {\n    headers.Authorization = `Basic ${Buffer.from(\n      `${privateWidgetOptions.username}:${privateWidgetOptions.password}`,\n    ).toString(\"base64\")}`;\n  }\n  const params = { method: \"GET\", headers };\n\n  const [status, , data] = await httpProxy(apiUrl, params);\n\n  if (status === 401) {\n    errorMessage = `Authorization failure getting data from glances API. Data: ${data.toString()}`;\n    logger.error(errorMessage);","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/gethomepage/homepage/blob/b6dca1ae033e613d8e692f9a161a3cc53a5a2857/src/pages/api/widgets/glances.js#L1-L32","documentation":"Thrown by the Glances widget's data retriever when the per-widget configuration has no `url` field. Homepage proxies metrics from a self-hosted Glances instance, so it needs to know where that instance lives.","triggerScenarios":"retrieveFromGlancesAPI() is called with privateWidgetOptions whose `url` is falsy (`if (!url)`). Happens on GET /api/widgets/glances?index=N when the widget at slot N (or its service entry) has no url configured.","commonSituations":"Newly added Glances widget saved without filling the URL; URL stored under a different key (e.g. `endpoint`); config migration dropped the field; service entry references the widget but the widget block is empty.","solutions":["Open the Glances widget settings in Homepage and set the URL field (e.g. http://glances:61108).","If configuring via services.yaml, ensure the widget entry under your service has `url:` set.","Verify the widget index in the request maps to a configured slot.","Check that the value isn't an empty string — empty is treated as missing."],"exampleFix":"// before (services.yaml)\nservices:\n  - My Group:\n      - My Server:\n          widget:\n            type: glances\n            # url missing\n\n// after\nservices:\n  - My Group:\n      - My Server:\n          widget:\n            type: glances\n            url: http://glances:61108","handlingStrategy":"validation","validationCode":"function validateGlancesWidgetOptions(opts) {\n  if (!opts || typeof opts.url !== 'string' || !opts.url.trim()) {\n    return [{ field: 'url', message: 'Glances widget requires a url' }];\n  }\n  return [];\n}","typeGuard":"function hasGlancesUrl(opts) {\n  return Boolean(opts && typeof opts.url === 'string' && opts.url.trim());\n}","tryCatchPattern":null,"preventionTips":["Validate widget options before saving the Homepage config.","Treat url as required for all proxy-style widgets.","Provide a config template that ships with the url field pre-flagged.","Smoke-test the widget right after adding it."],"tags":["widget","glances","configuration","validation"],"backgroundTag":null,"analyzedSha":"b6dca1ae033e613d8e692f9a161a3cc53a5a2857","analyzedAt":"2026-08-13T04:48:44.121Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}