Graph widgets need consistent value scaling (kB/MB/GB, kbps/Mbps, …). Add
shared `unit` (none/bytes/bytes_per_sec/bits_per_sec/bits/percent/seconds) and
`scale` (auto/k/m/g/t) enum fields to:
- PrometheusChartWidgetConfig (alongside promql/window)
- new QbittorrentSpeedWidgetConfig — the speed widget previously had NO config
options at all; totals/active keep their empty config.
Declared as Pydantic Literal enums so the widget-kind JSON schema exposes
`enum`, which the frontend config dialog renders as a dropdown. The data
sources are unchanged (raw values); scaling is a display concern handled
client-side. qBittorrent speed defaults to bytes/sec.
Test: chart widget kinds expose the shared unit/scale enums; speed defaults to
bytes_per_sec; totals/active stay option-less. 389/389 backend tests pass.
The credential tester always reported "Authentication failed — qBittorrent
rejected the credentials" for the qBittorrent service, even when credentials
were correct. test_connection classified any RuntimeError whose message
contained "login failed" as an auth failure — and the gateway-timeout error
(502/503/504 from the reverse proxy) and the wrong-URL diagnostic both started
with "qBittorrent login failed:", so a proxy timeout was reported as a
credentials rejection. That sent users down the wrong path (re-entering correct
passwords to fix a 504).
- QbittorrentClient._login: gateway and URL/routing errors no longer contain
"login failed"; only a genuine "Fails." body carries the
"invalid username or password" signal.
- integrations/qbittorrent.test_connection: key the auth message off
"invalid username or password" specifically; all other login errors flow
through translate_connection_error so the real reason (proxy timeout, wrong
URL, empty body) is surfaced.
After this, a failing test reports the actual cause (e.g. "qBittorrent is
unreachable: reverse proxy returned HTTP 504 ...") instead of accusing the
credentials. New regression test asserts a gateway error is NOT reported as
"Authentication failed". 386/386 backend tests pass; ruff clean.
Every HTTP client passed an integer timeout to requests, applying the same
value to BOTH connect and read phases. A slow Jellyfin /Items page or qBit
/sync/maindata blew through the 10s read budget → ReadTimeoutError. Split
into a (connect=5s, read=60s default) tuple via shared http_timeout() helper.
The media index build worker uses a 180s read floor. Existing services with
low timeout_seconds benefit from bumping to 60+.