refactor(server): centralize remote address selection

Unify remote address resolution so startup logging, primary remote URL selection, and /api/meta all consume the same server-side policy. Keep all external addresses user-visible, preserve stable interface ordering, and only de-prioritize link-local addresses when choosing the primary recommended remote URL.

On the UI side, introduce a shared helper that keeps the first remote address visible by default and collapses the remaining addresses behind a reveal action, with i18n coverage and targeted tests for the new selection behavior.
This commit is contained in:
VooDisss
2026-04-01 17:03:41 +03:00
parent 984743f3c7
commit b0b0a55e14
15 changed files with 241 additions and 53 deletions

View File

@@ -41,6 +41,8 @@ export const remoteAccessMessages = {
"remoteAccess.sections.addresses.help": "Launch or scan from another machine to hand over control.",
"remoteAccess.addresses.loading": "Loading addresses…",
"remoteAccess.addresses.none": "No addresses available yet.",
"remoteAccess.addresses.actions.showOther": "Show {count} other addresses",
"remoteAccess.addresses.actions.hideOther": "Hide other addresses",
"remoteAccess.address.scope.network": "Network",
"remoteAccess.address.scope.loopback": "Loopback",
"remoteAccess.address.scope.internal": "Internal",

View File

@@ -41,6 +41,8 @@ export const remoteAccessMessages = {
"remoteAccess.sections.addresses.help": "Abre o escanea desde otra máquina para transferir el control.",
"remoteAccess.addresses.loading": "Cargando direcciones…",
"remoteAccess.addresses.none": "Aún no hay direcciones disponibles.",
"remoteAccess.addresses.actions.showOther": "Mostrar {count} direcciones más",
"remoteAccess.addresses.actions.hideOther": "Ocultar otras direcciones",
"remoteAccess.address.scope.network": "Red",
"remoteAccess.address.scope.loopback": "Loopback",
"remoteAccess.address.scope.internal": "Interna",

View File

@@ -41,6 +41,8 @@ export const remoteAccessMessages = {
"remoteAccess.sections.addresses.help": "Lancez ou scannez depuis une autre machine pour passer le contrôle.",
"remoteAccess.addresses.loading": "Chargement des adresses…",
"remoteAccess.addresses.none": "Aucune adresse disponible pour le moment.",
"remoteAccess.addresses.actions.showOther": "Afficher {count} autres adresses",
"remoteAccess.addresses.actions.hideOther": "Masquer les autres adresses",
"remoteAccess.address.scope.network": "Réseau",
"remoteAccess.address.scope.loopback": "Boucle locale",
"remoteAccess.address.scope.internal": "Interne",

View File

@@ -41,6 +41,8 @@ export const remoteAccessMessages = {
"remoteAccess.sections.addresses.help": "הפעל או סרוק ממכונה אחרת להעברת שליטה.",
"remoteAccess.addresses.loading": "טוען כתובות…",
"remoteAccess.addresses.none": "אין כתובות זמינות עדיין.",
"remoteAccess.addresses.actions.showOther": "הצג עוד {count} כתובות",
"remoteAccess.addresses.actions.hideOther": "הסתר כתובות נוספות",
"remoteAccess.address.scope.network": "רשת",
"remoteAccess.address.scope.loopback": "לולאה מקומית",
"remoteAccess.address.scope.internal": "פנימי",

View File

@@ -41,6 +41,8 @@ export const remoteAccessMessages = {
"remoteAccess.sections.addresses.help": "別の端末から起動またはスキャンして操作を引き継ぎます。",
"remoteAccess.addresses.loading": "アドレスを読み込み中…",
"remoteAccess.addresses.none": "まだ利用可能なアドレスがありません。",
"remoteAccess.addresses.actions.showOther": "他の {count} 件のアドレスを表示",
"remoteAccess.addresses.actions.hideOther": "他のアドレスを隠す",
"remoteAccess.address.scope.network": "ネットワーク",
"remoteAccess.address.scope.loopback": "ループバック",
"remoteAccess.address.scope.internal": "内部",

View File

@@ -41,6 +41,8 @@ export const remoteAccessMessages = {
"remoteAccess.sections.addresses.help": "Откройте или отсканируйте с другой машины, чтобы передать управление.",
"remoteAccess.addresses.loading": "Загрузка адресов…",
"remoteAccess.addresses.none": "Пока нет доступных адресов.",
"remoteAccess.addresses.actions.showOther": "Показать еще {count} адресов",
"remoteAccess.addresses.actions.hideOther": "Скрыть остальные адреса",
"remoteAccess.address.scope.network": "Сеть",
"remoteAccess.address.scope.loopback": "Loopback",
"remoteAccess.address.scope.internal": "Внутренний",

View File

@@ -41,6 +41,8 @@ export const remoteAccessMessages = {
"remoteAccess.sections.addresses.help": "从另一台设备打开或扫描,以接管控制权。",
"remoteAccess.addresses.loading": "正在加载地址…",
"remoteAccess.addresses.none": "暂时没有可用地址。",
"remoteAccess.addresses.actions.showOther": "显示另外 {count} 个地址",
"remoteAccess.addresses.actions.hideOther": "隐藏其他地址",
"remoteAccess.address.scope.network": "网络",
"remoteAccess.address.scope.loopback": "回环",
"remoteAccess.address.scope.internal": "内部",

View File

@@ -0,0 +1,17 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"
import { splitRemoteAddresses } from "./remote-access-addresses"
describe("splitRemoteAddresses", () => {
it("keeps the first remote address visible and collapses the rest", () => {
const result = splitRemoteAddresses([
{ ip: "127.0.0.1", family: "ipv4", scope: "loopback", remoteUrl: "https://127.0.0.1:9898" },
{ ip: "192.168.1.128", family: "ipv4", scope: "external", remoteUrl: "https://192.168.1.128:9898" },
{ ip: "172.24.96.1", family: "ipv4", scope: "external", remoteUrl: "https://172.24.96.1:9898" },
])
assert.equal(result.recommended?.ip, "192.168.1.128")
assert.deepEqual(result.hidden.map((address) => address.ip), ["172.24.96.1"])
})
})

View File

@@ -0,0 +1,14 @@
import type { NetworkAddress } from "../../../server/src/api-types"
export interface RemoteAddressGroups {
recommended: NetworkAddress | null
hidden: NetworkAddress[]
}
export function splitRemoteAddresses(addresses: NetworkAddress[]): RemoteAddressGroups {
const remoteAddresses = addresses.filter((address) => address.scope !== "loopback")
return {
recommended: remoteAddresses[0] ?? null,
hidden: remoteAddresses.slice(1),
}
}