fix(services): split a comma-listed host override and type the wire host
The frontend takes the first host of a comma-separated override (`splitFirstHost`), the backend took the whole string — so a two-host override checked a hostname the UI never links to. `_parse_override` now splits the same way. The WebSocket service-status entry also declares `host` (nullable, as the backend sends it), so the overlay key stops relying on an undeclared field. ha-relevant: yes
This commit is contained in:
committed by
Pouzor - Rémy Jardient
parent
a93b0408b8
commit
c796cb39bd
@@ -145,7 +145,9 @@ def _parse_override(raw: str) -> tuple[str, str | None, int | None]:
|
||||
a service may carry its own host, optionally with a scheme and a port
|
||||
(`blog.example.com`, `blog.example.com:8443`, `https://blog.example.com`).
|
||||
"""
|
||||
rest = raw
|
||||
# Like a node ip, an override may list several hosts — the first one wins,
|
||||
# same as the frontend's `splitFirstHost`.
|
||||
rest = raw.split(",")[0].strip()
|
||||
scheme: str | None = None
|
||||
for prefix in ("https://", "http://"):
|
||||
if rest.lower().startswith(prefix):
|
||||
|
||||
@@ -527,6 +527,12 @@ async def test_check_service_lets_the_service_port_beat_the_override_port():
|
||||
assert await _captured_url(svc, "10.0.0.1") == "http://blog.example.com:3000"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_service_uses_the_first_host_of_a_comma_list_override():
|
||||
svc = {"port": 8080, "protocol": "tcp", "service_name": "blog", "host": "blog.example.com, alt.example.com"}
|
||||
assert await _captured_url(svc, "10.0.0.1") == "http://blog.example.com:8080"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_service_brackets_an_ipv6_override():
|
||||
svc = {"port": 80, "protocol": "tcp", "service_name": "http", "host": "[2001:db8::1]:8080"}
|
||||
|
||||
@@ -6,6 +6,9 @@ import type { ServiceStatus } from '@/types'
|
||||
interface ServiceStatusEntry {
|
||||
port?: number
|
||||
protocol?: string
|
||||
/** Per-service host override, part of the overlay key — several vhosts can
|
||||
* share one port on one node. Null when the service has none. */
|
||||
host?: string | null
|
||||
status: ServiceStatus
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,12 @@ describe('canvasStore — nodes', () => {
|
||||
expect(serviceStatuses['node-1:443/tcp@shop.example.com']).toBe('offline')
|
||||
})
|
||||
|
||||
it('setServiceStatuses treats a null host from the wire as no override', () => {
|
||||
const { setServiceStatuses } = useCanvasStore.getState()
|
||||
setServiceStatuses('node-1', [{ port: 80, protocol: 'tcp', host: null, status: 'online' }])
|
||||
expect(useCanvasStore.getState().serviceStatuses['node-1:80/tcp@']).toBe('online')
|
||||
})
|
||||
|
||||
it('setServiceStatuses merges without dropping other nodes', () => {
|
||||
const { setServiceStatuses } = useCanvasStore.getState()
|
||||
setServiceStatuses('node-1', [{ port: 80, protocol: 'tcp', status: 'online' }])
|
||||
|
||||
@@ -104,7 +104,7 @@ function translateWaypointsForMovedNodes(
|
||||
/** Key for the live per-service status overlay.
|
||||
* `host` is part of the key because several vhosts can share one port on one
|
||||
* node — without it they'd all read the same status. */
|
||||
export const serviceStatusKey = (nodeId: string, port?: number, protocol?: string, host?: string): string =>
|
||||
export const serviceStatusKey = (nodeId: string, port?: number, protocol?: string, host?: string | null): string =>
|
||||
`${nodeId}:${port ?? ''}/${protocol ?? ''}@${host?.trim() ?? ''}`
|
||||
|
||||
interface CanvasState {
|
||||
@@ -188,7 +188,7 @@ interface CanvasState {
|
||||
fitViewPending: boolean
|
||||
clearFitViewPending: () => void
|
||||
notifyScanDeviceFound: () => void
|
||||
setServiceStatuses: (nodeId: string, statuses: { port?: number; protocol?: string; host?: string; status: ServiceStatus }[]) => void
|
||||
setServiceStatuses: (nodeId: string, statuses: { port?: number; protocol?: string; host?: string | null; status: ServiceStatus }[]) => void
|
||||
hideIp: boolean
|
||||
toggleHideIp: () => void
|
||||
setHideIp: (value: boolean) => void
|
||||
|
||||
Reference in New Issue
Block a user