From eadfdaacad4921e8bd903953c69a026e06182f76 Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Wed, 23 Sep 2026 22:56:25 -0400 Subject: [PATCH] Show the API path hint when a bare address redirects --- ui/admin/index.html | 2 +- ui/js/admin-app-form.js | 8 ++++---- ui/js/admin-auth.js | 2 +- ui/js/admin-color-control.js | 2 +- ui/js/admin-list.js | 6 +++--- ui/js/admin-logic.js | 11 ++++------- ui/js/admin-settings.js | 6 +++--- ui/js/admin-shared.js | 2 +- ui/js/admin-widget-form.js | 8 ++++---- ui/js/admin.js | 16 ++++++++-------- ui/js/listbox.js | 2 +- ui/js/widget-config-form.js | 8 ++++---- ui/test/admin-logic.test.mjs | 8 +++----- 13 files changed, 38 insertions(+), 43 deletions(-) diff --git a/ui/admin/index.html b/ui/admin/index.html index 21ec66c9..9af5445c 100644 --- a/ui/admin/index.html +++ b/ui/admin/index.html @@ -292,6 +292,6 @@

General

- + diff --git a/ui/js/admin-app-form.js b/ui/js/admin-app-form.js index 76906926..e4751d97 100644 --- a/ui/js/admin-app-form.js +++ b/ui/js/admin-app-form.js @@ -10,7 +10,7 @@ import { failureIsMissingApiPath, nextActiveIndex, sameIconName, -} from '/js/admin-logic.js?v=5356a1b3'; +} from '/js/admin-logic.js?v=cbb7417d'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { toast, @@ -21,10 +21,10 @@ import { reveal, setTogDisabled, swapContent, -} from '/js/admin-shared.js?v=d218efee'; -import { createListbox } from '/js/listbox.js?v=80606092'; +} from '/js/admin-shared.js?v=7407b339'; +import { createListbox } from '/js/listbox.js?v=ccee5041'; import { MAX_LABELS } from '/js/badge-logic.js?v=9e6d9d4b'; -import { renderColorControl, BADGE_DEFAULT } from '/js/admin-color-control.js?v=96364a80'; +import { renderColorControl, BADGE_DEFAULT } from '/js/admin-color-control.js?v=6f0cd148'; import { badgeErrorAdvice, TONE } from '/js/admin-error.js?v=e4ea7820'; import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; diff --git a/ui/js/admin-auth.js b/ui/js/admin-auth.js index d8df95be..985cfd23 100644 --- a/ui/js/admin-auth.js +++ b/ui/js/admin-auth.js @@ -1,4 +1,4 @@ -import { apiGet, apiPost } from '/js/admin-shared.js?v=d218efee'; +import { apiGet, apiPost } from '/js/admin-shared.js?v=7407b339'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; import { el, inp as inpById, qa } from '/js/utils.js?v=ed0f1802'; diff --git a/ui/js/admin-color-control.js b/ui/js/admin-color-control.js index 1972d2cc..3e5fce10 100644 --- a/ui/js/admin-color-control.js +++ b/ui/js/admin-color-control.js @@ -1,4 +1,4 @@ -import { PE_SVG, initInlineEdit, toast, reveal } from '/js/admin-shared.js?v=d218efee'; +import { PE_SVG, initInlineEdit, toast, reveal } from '/js/admin-shared.js?v=7407b339'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { qa, q } from '/js/utils.js?v=ed0f1802'; diff --git a/ui/js/admin-list.js b/ui/js/admin-list.js index 0f4a1b7b..6ec75740 100644 --- a/ui/js/admin-list.js +++ b/ui/js/admin-list.js @@ -13,13 +13,13 @@ import { collapsedFolders, filter, state } from '/js/admin-state.js?v=831e219e'; import { snapshotItems } from '/js/admin-save-logic.js?v=60a82419'; -import { reorderItems } from '/js/admin-logic.js?v=5356a1b3'; +import { reorderItems } from '/js/admin-logic.js?v=cbb7417d'; import { initDrag, wireRowDrag } from '/js/admin-drag.js?v=0a411097'; -import { paintIcon } from '/js/admin-shared.js?v=d218efee'; +import { paintIcon } from '/js/admin-shared.js?v=7407b339'; import { clr, el, qa, setUserText } from '/js/utils.js?v=ed0f1802'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { t } from '/js/i18n.js?v=1f1ea9c1'; -import { sizeLabel } from '/js/admin-widget-form.js?v=e17ed1b7'; +import { sizeLabel } from '/js/admin-widget-form.js?v=0112370e'; import { widgetGlyph } from '/js/widget-glyphs.js?v=ad1869d7'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; diff --git a/ui/js/admin-logic.js b/ui/js/admin-logic.js index c2b61769..f183948b 100644 --- a/ui/js/admin-logic.js +++ b/ui/js/admin-logic.js @@ -305,12 +305,9 @@ export function isBareHostUrl(url) { /** Whether a failed badge fetch should report the missing API path instead of the failure itself. - The hint is a guess from the address. The error code is what the service - reported. A guess never replaces a known cause, so the hint is shown only - where a missing path could actually explain the failure: the service - answered and had nothing there (404), or answered with something that is not - data. A request that was blocked, never connected, or was refused for - credentials failed for a reason the path cannot change. + Shown only where a missing path explains the failure: a bare address that + answered 404 or redirected, usually to a login page. Anything else is + reported as itself. @param {string} url @param {{ sessionExpired?: boolean, code?: string, vars?: { status?: number } }} advice @@ -319,7 +316,7 @@ export function failureIsMissingApiPath(url, advice) { if (!advice || advice.sessionExpired) return false; if (!isBareHostUrl(url)) return false; if (advice.code === 'upstream.status') return advice.vars?.status === 404; - return advice.code === 'invalid'; + return advice.code === 'upstream.redirect'; } /** Compares what `read` returns now with what it returned at the last reset. diff --git a/ui/js/admin-settings.js b/ui/js/admin-settings.js index 8a3fca31..e83baca9 100644 --- a/ui/js/admin-settings.js +++ b/ui/js/admin-settings.js @@ -1,4 +1,4 @@ -import { toast, apiGet, apiPost, reveal, swapContent } from '/js/admin-shared.js?v=d218efee'; +import { toast, apiGet, apiPost, reveal, swapContent } from '/js/admin-shared.js?v=7407b339'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { @@ -7,10 +7,10 @@ import { clearsStoredPassword, createDirtyTracker, BLOCK, -} from '/js/admin-logic.js?v=5356a1b3'; +} from '/js/admin-logic.js?v=cbb7417d'; import { confirmText } from '/js/modal.js?v=11fa1eff'; import { el, inp, setUserText } from '/js/utils.js?v=ed0f1802'; -import { renderColorControl } from '/js/admin-color-control.js?v=96364a80'; +import { renderColorControl } from '/js/admin-color-control.js?v=6f0cd148'; import { BACKDROP } from '/js/background.js?v=848da677'; /* Mirrors the server's rule: auth cannot be switched on with no password. */ diff --git a/ui/js/admin-shared.js b/ui/js/admin-shared.js index 264cf35a..0a519bc4 100644 --- a/ui/js/admin-shared.js +++ b/ui/js/admin-shared.js @@ -1,5 +1,5 @@ /* Stateless helpers shared by the admin modules. Mutable state stays out. */ -import { recoversSession, toastHoldMs } from '/js/admin-logic.js?v=5356a1b3'; +import { recoversSession, toastHoldMs } from '/js/admin-logic.js?v=cbb7417d'; import { el, q } from '/js/utils.js?v=ed0f1802'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { iconChain } from '/js/icons.js?v=9c8c550c'; diff --git a/ui/js/admin-widget-form.js b/ui/js/admin-widget-form.js index 9288fd5c..97d9bdd5 100644 --- a/ui/js/admin-widget-form.js +++ b/ui/js/admin-widget-form.js @@ -1,9 +1,9 @@ import { state } from '/js/admin-state.js?v=831e219e'; -import { PE_SVG, initInlineEdit, swapContent } from '/js/admin-shared.js?v=d218efee'; -import { createListbox } from '/js/listbox.js?v=80606092'; -import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=0dbce7d5'; +import { PE_SVG, initInlineEdit, swapContent } from '/js/admin-shared.js?v=7407b339'; +import { createListbox } from '/js/listbox.js?v=ccee5041'; +import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=f6c23560'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { sizesForView, widgetConfigMode, rejectionLines, carriesTypedValues } from '/js/admin-logic.js?v=5356a1b3'; +import { sizesForView, widgetConfigMode, rejectionLines, carriesTypedValues } from '/js/admin-logic.js?v=cbb7417d'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { q, qi, qa } from '/js/utils.js?v=ed0f1802'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; diff --git a/ui/js/admin.js b/ui/js/admin.js index 526501f4..dcb7f3d0 100644 --- a/ui/js/admin.js +++ b/ui/js/admin.js @@ -1,7 +1,7 @@ -import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=097f38ca'; -import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=fbd8a6cb'; -import { initList, render, syncFilterUI } from '/js/admin-list.js?v=1c8871af'; -import { resolveAdminSection } from '/js/admin-logic.js?v=5356a1b3'; +import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=bc1dd21c'; +import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=3a99f588'; +import { initList, render, syncFilterUI } from '/js/admin-list.js?v=33904226'; +import { resolveAdminSection } from '/js/admin-logic.js?v=cbb7417d'; import { buildAppItem, claimFolderChildren, @@ -10,7 +10,7 @@ import { snapshotItems, upsertItem, } from '/js/admin-save-logic.js?v=60a82419'; -import { loadSettings, settingsDirty, showBgFields, showWallpaperFile } from '/js/admin-settings.js?v=b5694a5f'; +import { loadSettings, settingsDirty, showBgFields, showWallpaperFile } from '/js/admin-settings.js?v=d70ae980'; import { apiGet, apiPost, @@ -19,12 +19,12 @@ import { reveal, setReauthHandler, toast, -} from '/js/admin-shared.js?v=d218efee'; +} from '/js/admin-shared.js?v=7407b339'; import { collapsedFolders, filter, state } from '/js/admin-state.js?v=831e219e'; -import { buildWidgetForm } from '/js/admin-widget-form.js?v=e17ed1b7'; +import { buildWidgetForm } from '/js/admin-widget-form.js?v=0112370e'; import { initFluidHover } from '/js/fluid-hover.js?v=cb886e86'; import { initGlideSelect, syncGlideSelect } from '/js/glide-select.js?v=8b39e9d0'; -import { createListbox } from '/js/listbox.js?v=80606092'; +import { createListbox } from '/js/listbox.js?v=ccee5041'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { initI18n, LANGUAGES, t } from '/js/i18n.js?v=1f1ea9c1'; import { loadLocalIcons } from '/js/icons.js?v=9c8c550c'; diff --git a/ui/js/listbox.js b/ui/js/listbox.js index 83c04291..1bed14cf 100644 --- a/ui/js/listbox.js +++ b/ui/js/listbox.js @@ -6,7 +6,7 @@ Do not use popover. The top layer makes iOS collapse its browser toolbar, which exposes a strip of page canvas and misplaces the menu. */ -import { nextActiveIndex } from '/js/admin-logic.js?v=5356a1b3'; +import { nextActiveIndex } from '/js/admin-logic.js?v=cbb7417d'; import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { qa } from '/js/utils.js?v=ed0f1802'; diff --git a/ui/js/widget-config-form.js b/ui/js/widget-config-form.js index e032a528..89b395e8 100644 --- a/ui/js/widget-config-form.js +++ b/ui/js/widget-config-form.js @@ -3,9 +3,9 @@ import { t } from '/js/i18n.js?v=1f1ea9c1'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { reveal, wireInlineEdit } from '/js/admin-shared.js?v=d218efee'; -import { createListbox } from '/js/listbox.js?v=80606092'; -import { renderColorControl } from '/js/admin-color-control.js?v=96364a80'; +import { reveal, wireInlineEdit } from '/js/admin-shared.js?v=7407b339'; +import { createListbox } from '/js/listbox.js?v=ccee5041'; +import { renderColorControl } from '/js/admin-color-control.js?v=6f0cd148'; import { seedCarried, applyOptionSet, @@ -13,7 +13,7 @@ import { requiredFieldMissing, groupBounds, visibleFieldFlags, -} from '/js/admin-logic.js?v=5356a1b3'; +} from '/js/admin-logic.js?v=cbb7417d'; import { optionsErrorAdvice, TONE } from '/js/admin-error.js?v=e4ea7820'; import { qi } from '/js/utils.js?v=ed0f1802'; diff --git a/ui/test/admin-logic.test.mjs b/ui/test/admin-logic.test.mjs index 0cac9a5a..0ed49fc8 100644 --- a/ui/test/admin-logic.test.mjs +++ b/ui/test/admin-logic.test.mjs @@ -576,12 +576,10 @@ test('isBareHostUrl rejects what it cannot parse', () => { for (const u of ['', ' ', 'http://', undefined, null, 42]) assert.equal(isBareHostUrl(u), false, String(u)); }); -/* The two failures a missing path actually produces: the service answers and - has nothing there, or answers with something that is not data. */ test('a failed fetch of a bare address reports the missing API path', () => { const notFound = { code: 'upstream.status', vars: { status: 404 } }; - const notData = { code: 'invalid' }; - for (const advice of [notFound, notData]) { + const redirected = { code: 'upstream.redirect', vars: { status: 302 } }; + for (const advice of [notFound, redirected]) { assert.equal(failureIsMissingApiPath('https://seerr.example.com', advice), true, advice.code); assert.equal(failureIsMissingApiPath('https://seerr.example.com/api/v1/request/count', advice), false); } @@ -598,7 +596,7 @@ test('a known cause is reported instead of the path hint', () => { { code: 'timeout' }, { code: 'network.tls-untrusted' }, { code: 'invalid.retype' }, - { code: 'upstream.redirect', vars: { status: 301 } }, + { code: 'invalid' }, { code: 'upstream.status', vars: { status: 401 } }, { code: 'upstream.status', vars: { status: 500 } }, ]) {