From 85a848e8b661ea855a8e3a3ac7244573e72fcbc3 Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Sat, 17 Oct 2026 10:16:03 -0400 Subject: [PATCH] Add optional Scrutiny API token to Disk Health Send the token as a Bearer header on the summary request and the bay picker when it is set. Scrutiny 2.0 requires a token by default. --- api/test/disk-health-data.test.js | 14 ++++++++++++++ ui/widgets/disk-health/data.js | 13 ++++++++++--- ui/widgets/disk-health/i18n/de.json | 2 ++ ui/widgets/disk-health/i18n/en.json | 2 ++ ui/widgets/disk-health/i18n/es.json | 2 ++ ui/widgets/disk-health/i18n/fa.json | 2 ++ ui/widgets/disk-health/i18n/fr.json | 2 ++ ui/widgets/disk-health/i18n/zh-Hans.json | 2 ++ ui/widgets/disk-health/widget.json | 11 +++++++++++ 9 files changed, 47 insertions(+), 3 deletions(-) diff --git a/api/test/disk-health-data.test.js b/api/test/disk-health-data.test.js index c18ba26b..74837fcd 100644 --- a/api/test/disk-health-data.test.js +++ b/api/test/disk-health-data.test.js @@ -160,3 +160,17 @@ test('with no link set the widget opens the Scrutiny address the browser can rea 'https://disks.example.com', ); }); + +for (const endpoint of ['', 'devices']) { + test(`${endpoint || 'the bay list'} sends the Scrutiny token as a Bearer header only when one is set`, async () => { + const seen = []; + const fetchJSON = async (url, opts) => { + seen.push(opts.headers); + return { status: 200, data: { data: { summary: FORK } } }; + }; + const base = { diskProvider: 'scrutiny', scrutinyUrl: 'http://s.local', bays: [] }; + await dataFn(makeCtx(endpoint, { ...base, scrutinyToken: 'tok' }, fetchJSON)); + await dataFn(makeCtx(endpoint, base, fetchJSON)); + assert.deepEqual(seen, [{ Authorization: 'Bearer tok' }, {}]); + }); +} diff --git a/ui/widgets/disk-health/data.js b/ui/widgets/disk-health/data.js index 14f7796b..2f7473f7 100644 --- a/ui/widgets/disk-health/data.js +++ b/ui/widgets/disk-health/data.js @@ -20,8 +20,12 @@ function scrutinyDeviceIds(key, device) { return [device?.device_id, device?.scrutiny_uuid, device?.wwn, key].filter(v => typeof v === 'string' && v); } +function scrutinyHeaders(config) { + return config.scrutinyToken ? { Authorization: 'Bearer ' + config.scrutinyToken } : {}; +} + function scrutinyStatus(ctx, r) { - if (r.status === 401 || r.status === 403) ctx.fail('Scrutiny auth failed', { kind: ctx.KIND.AUTH }); + if (r.status === 401 || r.status === 403) ctx.fail('Scrutiny auth failed, check API token', { kind: ctx.KIND.AUTH }); if (r.status >= 400) ctx.fail('Scrutiny HTTP ' + r.status); } @@ -40,7 +44,10 @@ function scrutinyIndex(summary) { async function scrutinyDeviceOptions(ctx) { const { config, fetchJSON, normalizeBase } = ctx; if (!config.scrutinyUrl) ctx.fail('Enter the Scrutiny URL first.', { kind: ctx.KIND.INVALID }); - const r = await fetchJSON(normalizeBase(config.scrutinyUrl) + '/api/summary', { timeout: 8000 }); + const r = await fetchJSON(normalizeBase(config.scrutinyUrl) + '/api/summary', { + headers: scrutinyHeaders(config), + timeout: 8000, + }); scrutinyStatus(ctx, r); const summary = r.data?.data?.summary || {}; const options = Object.entries(summary) @@ -90,7 +97,7 @@ async function diskHealthScrutiny(ctx) { const bays = config.bays || []; const base = normalizeBase(url); - const r = await fetchJSON(base + '/api/summary', { timeout: 8000 }); + const r = await fetchJSON(base + '/api/summary', { headers: scrutinyHeaders(config), timeout: 8000 }); scrutinyStatus(ctx, r); const byId = scrutinyIndex(r.data?.data?.summary || {}); diff --git a/ui/widgets/disk-health/i18n/de.json b/ui/widgets/disk-health/i18n/de.json index 8ca5d572..d9754d66 100644 --- a/ui/widgets/disk-health/i18n/de.json +++ b/ui/widgets/disk-health/i18n/de.json @@ -6,6 +6,8 @@ "diskProvider.opt.truenas": "TrueNAS", "scrutinyUrl.label": "Scrutiny-URL", "scrutinyUrl.placeholder": "scrutiny:8080", + "scrutinyToken.label": "Scrutiny-API-Token", + "scrutinyToken.hint": "Nötig, wenn Scrutiny eine Anmeldung verlangt. Ab Scrutiny 2.0 ist das die Vorgabe. Ein Token mit dem Bereich read genügt.", "scrutinyHref.label": "Link-URL", "scrutinyHref.placeholder": "http://your-server:8080", "scrutinyHref.hint": "Die Adresse, unter der dein Browser diesen Dienst öffnet. Leer lassen, um die Adresse oben zu verwenden, sofern dein Browser sie erreicht.", diff --git a/ui/widgets/disk-health/i18n/en.json b/ui/widgets/disk-health/i18n/en.json index 0fb75a90..41d0d8ea 100644 --- a/ui/widgets/disk-health/i18n/en.json +++ b/ui/widgets/disk-health/i18n/en.json @@ -6,6 +6,8 @@ "diskProvider.opt.truenas": "TrueNAS", "scrutinyUrl.label": "Scrutiny URL", "scrutinyUrl.placeholder": "scrutiny:8080", + "scrutinyToken.label": "Scrutiny API Token", + "scrutinyToken.hint": "Needed when Scrutiny requires a login, which is the default from Scrutiny 2.0. A token with read scope is enough.", "scrutinyHref.label": "Link URL", "scrutinyHref.placeholder": "http://your-server:8080", "scrutinyHref.hint": "The address your browser uses for this service. Leave blank to use the one above, if your browser can reach it.", diff --git a/ui/widgets/disk-health/i18n/es.json b/ui/widgets/disk-health/i18n/es.json index 1d55b6ac..1d5ce914 100644 --- a/ui/widgets/disk-health/i18n/es.json +++ b/ui/widgets/disk-health/i18n/es.json @@ -6,6 +6,8 @@ "diskProvider.opt.truenas": "TrueNAS", "scrutinyUrl.label": "URL de Scrutiny", "scrutinyUrl.placeholder": "scrutiny:8080", + "scrutinyToken.label": "Token de API de Scrutiny", + "scrutinyToken.hint": "Necesario si Scrutiny exige iniciar sesión, lo predeterminado desde Scrutiny 2.0. Basta un token con el ámbito read.", "scrutinyHref.label": "URL del enlace", "scrutinyHref.placeholder": "http://your-server:8080", "scrutinyHref.hint": "La dirección con la que tu navegador abre este servicio. Déjalo en blanco para usar la de arriba, si tu navegador puede acceder a ella.", diff --git a/ui/widgets/disk-health/i18n/fa.json b/ui/widgets/disk-health/i18n/fa.json index 3a58b429..db98acf7 100644 --- a/ui/widgets/disk-health/i18n/fa.json +++ b/ui/widgets/disk-health/i18n/fa.json @@ -6,6 +6,8 @@ "diskProvider.opt.truenas": "TrueNAS", "scrutinyUrl.label": "آدرس Scrutiny", "scrutinyUrl.placeholder": "scrutiny:8080", + "scrutinyToken.label": "توکن API Scrutiny", + "scrutinyToken.hint": "اگر Scrutiny ورود بخواهد لازم است. از Scrutiny 2.0 این حالت پیش‌فرض است. توکنی با دسترسی read کافی است.", "scrutinyHref.label": "آدرس پیوند", "scrutinyHref.placeholder": "http://your-server:8080", "scrutinyHref.hint": "آدرسی که مرورگرتان برای این سرویس استفاده می‌کند. اگر مرورگرتان به آدرس بالا دسترسی دارد، خالی بگذارید.", diff --git a/ui/widgets/disk-health/i18n/fr.json b/ui/widgets/disk-health/i18n/fr.json index 08c7815b..3b22ee1b 100644 --- a/ui/widgets/disk-health/i18n/fr.json +++ b/ui/widgets/disk-health/i18n/fr.json @@ -6,6 +6,8 @@ "diskProvider.opt.truenas": "TrueNAS", "scrutinyUrl.label": "URL Scrutiny", "scrutinyUrl.placeholder": "scrutiny:8080", + "scrutinyToken.label": "Jeton d’API Scrutiny", + "scrutinyToken.hint": "Nécessaire si Scrutiny exige une connexion, ce qui est le cas par défaut depuis Scrutiny 2.0. Un jeton avec la portée read suffit.", "scrutinyHref.label": "URL du lien", "scrutinyHref.placeholder": "http://your-server:8080", "scrutinyHref.hint": "L’adresse que votre navigateur utilise pour ce service. Laissez vide pour utiliser celle ci-dessus, si votre navigateur peut l’atteindre.", diff --git a/ui/widgets/disk-health/i18n/zh-Hans.json b/ui/widgets/disk-health/i18n/zh-Hans.json index 45563e3a..464c3007 100644 --- a/ui/widgets/disk-health/i18n/zh-Hans.json +++ b/ui/widgets/disk-health/i18n/zh-Hans.json @@ -6,6 +6,8 @@ "diskProvider.opt.truenas": "TrueNAS", "scrutinyUrl.label": "Scrutiny 地址", "scrutinyUrl.placeholder": "scrutiny:8080", + "scrutinyToken.label": "Scrutiny API 令牌", + "scrutinyToken.hint": "Scrutiny 要求登录时需要填写,Scrutiny 2.0 起默认要求登录。具有 read 权限的令牌即可。", "scrutinyHref.label": "链接地址", "scrutinyHref.placeholder": "http://your-server:8080", "scrutinyHref.hint": "浏览器访问此服务所用的地址。留空则使用上方地址(前提是浏览器能访问它)。", diff --git a/ui/widgets/disk-health/widget.json b/ui/widgets/disk-health/widget.json index af3b4008..f7cd1968 100644 --- a/ui/widgets/disk-health/widget.json +++ b/ui/widgets/disk-health/widget.json @@ -36,6 +36,17 @@ "equals": "scrutiny" } }, + { + "key": "scrutinyToken", + "type": "secret", + "label": "scrutinyToken.label", + "hint": "scrutinyToken.hint", + "optional": true, + "showIf": { + "field": "diskProvider", + "equals": "scrutiny" + } + }, { "key": "scrutinyHref", "type": "text",