feat(settings): unified server-URL resolver + field; adopt in Jellyseerr

Type a loose address (media.example.com, https://…, host:port) and the app finds the working, canonical URL.

- utils/serverUrl: generic candidate generator (https-first, port/path preserved, no Jellyfin-specific ports), parallel-probe resolver, numeric semver compare, and a Jellyseerr probe (/api/v1/status, min 2.0.0).

- useServerUrlResolver: idle -> resolving -> ok | error state machine with cancellation.

- ServerUrlField: shared input that auto-resolves on blur, inline status chip (tap to re-test) + resolved URL, persists the canonical URL.

- Jellyseerr settings adopt the field and log in with the resolved URL. Probe contract makes Streamystats/Jellyfin/Merlin a drop-in follow-up.
This commit is contained in:
Gauvain
2026-06-04 20:13:10 +02:00
parent 0d796d01b8
commit 0f29457ff8
10 changed files with 566 additions and 36 deletions

22
utils/serverUrl/semver.ts Normal file
View File

@@ -0,0 +1,22 @@
/**
* Strict numeric "below" comparison for dotted versions.
*
* Avoids the string-comparison bug (`"1.9.9" < "2.0.0"` works by luck but
* `"2.10.0" < "2.0.0"` is wrongly true). Non-numeric/pre-release suffixes on a
* segment are ignored (e.g. `2.0.0-beta` → 2.0.0).
*/
export function isVersionBelow(version: string, minimum: string): boolean {
const parse = (v: string) =>
v.split(".").map((segment) => Number.parseInt(segment, 10) || 0);
const a = parse(version);
const b = parse(minimum);
const length = Math.max(a.length, b.length);
for (let i = 0; i < length; i++) {
const x = a[i] ?? 0;
const y = b[i] ?? 0;
if (x !== y) return x < y;
}
return false;
}