security: parameterize node addresses; drop dead APP_URLS config
Demo images / Build & push demo images (push) Failing after 2m16s
Demo images / Build & push demo images (push) Failing after 2m16s
Keeps the dev and test tooling an outside contributor would want, and takes our node addresses out of it. Scripts that silently defaulted to one of our nodes now require an explicit host and exit 2 without one: smoke-test.sh, trust-archipelago-cert.sh, dev-container-test.sh (which also derives its RPC and health URLs from the SSH target instead of a second hardcoded copy), and image-recipe/dev-branding.sh. A default that points at a machine the user does not own is worse than no default: it fails confusingly, or reaches a stranger's device. Usage examples, mock data and test fixtures move to the RFC 5737 documentation range (192.0.2.0/24). CGNAT test values stay inside 100.64.0.0/10 so the range-check semantics they exercise still hold, and 192.168.1.0/.1/.254 are left alone — those are gateway logic and UI placeholders, not our addresses. Playwright and the perf spec defaulted their baseURL to one of our nodes; they now default to localhost:8100, the local dev server. Removed neode-ui APP_URLS entirely. It is dead code — exported, never imported — and it pinned fedimint's *prod* launch URL to 192.168.1.228:8175. Had anything consumed it, every user's node would have tried to reach an address that on their LAN is either nothing or someone else's machine. Deleting beats sanitizing dead config. Verified: frontend 868/868 vitest across 108 files; archipelago-container 75/75; mesh tests 9/9; audit-secrets 5/5. Zero node addresses and zero node names remain in tracked files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6ba0599639
commit
b2c7592840
@@ -25,7 +25,7 @@ describe('useAppLauncherStore', () => {
|
||||
vi.clearAllMocks()
|
||||
// Default to HTTP to avoid proxy rewriting
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { origin: 'http://192.168.1.228', protocol: 'http:', hostname: '192.168.1.228' },
|
||||
value: { origin: 'http://192.0.2.10', protocol: 'http:', hostname: '192.0.2.10' },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
@@ -71,8 +71,8 @@ describe('useAppLauncherStore', () => {
|
||||
|
||||
it('open() never falls through to the iframe overlay', () => {
|
||||
const store = useAppLauncherStore()
|
||||
store.open({ url: 'http://192.168.1.228:9999', title: 'Unknown app' })
|
||||
expect(openInApp).toHaveBeenCalledWith('http://192.168.1.228:9999')
|
||||
store.open({ url: 'http://192.0.2.10:9999', title: 'Unknown app' })
|
||||
expect(openInApp).toHaveBeenCalledWith('http://192.0.2.10:9999')
|
||||
expect(store.isOpen).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -81,7 +81,7 @@ describe('useAppLauncherStore', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
// Port 8083 maps to /app/filebrowser/ — should route to session
|
||||
store.open({ url: 'http://192.168.1.228:8083', title: 'FileBrowser' })
|
||||
store.open({ url: 'http://192.0.2.10:8083', title: 'FileBrowser' })
|
||||
|
||||
// Default panel mode: sets panelAppId, doesn't open overlay
|
||||
expect(store.isOpen).toBe(false)
|
||||
@@ -128,12 +128,12 @@ describe('useAppLauncherStore', () => {
|
||||
it('routes BTCPay (port 23000) to full-page session', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'http://192.168.1.228:23000', title: 'BTCPay' })
|
||||
store.open({ url: 'http://192.0.2.10:23000', title: 'BTCPay' })
|
||||
|
||||
expect(store.isOpen).toBe(false)
|
||||
expect(store.panelAppId).toBe(null)
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'http://192.168.1.228:23000',
|
||||
'http://192.0.2.10:23000',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
@@ -142,12 +142,12 @@ describe('useAppLauncherStore', () => {
|
||||
it('normalizes old Nginx Proxy Manager port 81 to 8081', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'http://192.168.1.228:81', title: 'Nginx Proxy Manager' })
|
||||
store.open({ url: 'http://192.0.2.10:81', title: 'Nginx Proxy Manager' })
|
||||
|
||||
expect(store.isOpen).toBe(false)
|
||||
expect(store.panelAppId).toBe(null)
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'http://192.168.1.228:8081',
|
||||
'http://192.0.2.10:8081',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
@@ -161,7 +161,7 @@ describe('useAppLauncherStore', () => {
|
||||
})
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'http://192.168.1.228:8081', title: 'Nginx Proxy Manager' })
|
||||
store.open({ url: 'http://192.0.2.10:8081', title: 'Nginx Proxy Manager' })
|
||||
|
||||
// Tab-only app on mobile-web: open directly in a new browser tab (the
|
||||
// companion would use the in-app WebView). No session, no route push, no
|
||||
@@ -170,7 +170,7 @@ describe('useAppLauncherStore', () => {
|
||||
expect(store.panelAppId).toBe(null)
|
||||
expect(mockPush).not.toHaveBeenCalled()
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'http://192.168.1.228:8081',
|
||||
'http://192.0.2.10:8081',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
@@ -179,10 +179,10 @@ describe('useAppLauncherStore', () => {
|
||||
it('opens Nginx Proxy Manager in new tab using title hint when URL is path-only', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'https://192.168.1.228/app/nginx-proxy-manager/', title: 'Nginx Proxy Manager' })
|
||||
store.open({ url: 'https://192.0.2.10/app/nginx-proxy-manager/', title: 'Nginx Proxy Manager' })
|
||||
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'https://192.168.1.228/app/nginx-proxy-manager/',
|
||||
'https://192.0.2.10/app/nginx-proxy-manager/',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
@@ -192,10 +192,10 @@ describe('useAppLauncherStore', () => {
|
||||
it('normalizes legacy Nginx Proxy Manager ports to 8081', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'http://192.168.1.228:8181', title: 'Nginx Proxy Manager' })
|
||||
store.open({ url: 'http://192.0.2.10:8181', title: 'Nginx Proxy Manager' })
|
||||
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'http://192.168.1.228:8081',
|
||||
'http://192.0.2.10:8081',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
@@ -204,10 +204,10 @@ describe('useAppLauncherStore', () => {
|
||||
it('normalizes legacy Uptime Kuma port 3001 to 3002', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'http://192.168.1.228:3001', title: 'Uptime Kuma' })
|
||||
store.open({ url: 'http://192.0.2.10:3001', title: 'Uptime Kuma' })
|
||||
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'http://192.168.1.228:3002',
|
||||
'http://192.0.2.10:3002',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
@@ -218,10 +218,10 @@ describe('useAppLauncherStore', () => {
|
||||
it('opens Uptime Kuma in new tab using title hint when URL is path-only', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'https://192.168.1.228/app/uptime-kuma/', title: 'Uptime Kuma' })
|
||||
store.open({ url: 'https://192.0.2.10/app/uptime-kuma/', title: 'Uptime Kuma' })
|
||||
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'https://192.168.1.228/app/uptime-kuma/',
|
||||
'https://192.0.2.10/app/uptime-kuma/',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
@@ -231,12 +231,12 @@ describe('useAppLauncherStore', () => {
|
||||
it('routes Home Assistant (port 8123) to full-page session', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'http://192.168.1.228:8123', title: 'Home Assistant' })
|
||||
store.open({ url: 'http://192.0.2.10:8123', title: 'Home Assistant' })
|
||||
|
||||
expect(store.isOpen).toBe(false)
|
||||
expect(store.panelAppId).toBe(null)
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'http://192.168.1.228:8123',
|
||||
'http://192.0.2.10:8123',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
@@ -245,12 +245,12 @@ describe('useAppLauncherStore', () => {
|
||||
it('routes Grafana (port 3000) to full-page session', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'http://192.168.1.228:3000', title: 'Grafana' })
|
||||
store.open({ url: 'http://192.0.2.10:3000', title: 'Grafana' })
|
||||
|
||||
expect(store.isOpen).toBe(false)
|
||||
expect(store.panelAppId).toBe(null)
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'http://192.168.1.228:3000',
|
||||
'http://192.0.2.10:3000',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
@@ -259,12 +259,12 @@ describe('useAppLauncherStore', () => {
|
||||
it('opens Gitea path URL in new tab', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'http://192.168.1.228/app/gitea/', title: 'Gitea' })
|
||||
store.open({ url: 'http://192.0.2.10/app/gitea/', title: 'Gitea' })
|
||||
|
||||
expect(store.isOpen).toBe(false)
|
||||
expect(store.panelAppId).toBe(null)
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'http://192.168.1.228/app/gitea/',
|
||||
'http://192.0.2.10/app/gitea/',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
@@ -273,7 +273,7 @@ describe('useAppLauncherStore', () => {
|
||||
it('does not map raw port 3001 to gitea session', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'http://192.168.1.228:3001', title: 'Unknown 3001' })
|
||||
store.open({ url: 'http://192.0.2.10:3001', title: 'Unknown 3001' })
|
||||
|
||||
expect(store.panelAppId).toBe(null)
|
||||
expect(store.isOpen).toBe(true)
|
||||
@@ -283,11 +283,11 @@ describe('useAppLauncherStore', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
// Use an unresolvable URL so it doesn't route to session
|
||||
store.open({ url: 'http://192.168.1.228:9999', title: 'Unknown', openInNewTab: true })
|
||||
store.open({ url: 'http://192.0.2.10:9999', title: 'Unknown', openInNewTab: true })
|
||||
|
||||
expect(store.isOpen).toBe(false)
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'http://192.168.1.228:9999',
|
||||
'http://192.0.2.10:9999',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
@@ -327,13 +327,13 @@ describe('useAppLauncherStore', () => {
|
||||
|
||||
it('routes HTTPS same-host apps via session view', () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { origin: 'https://192.168.1.228', protocol: 'https:', hostname: '192.168.1.228' },
|
||||
value: { origin: 'https://192.0.2.10', protocol: 'https:', hostname: '192.0.2.10' },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
store.open({ url: 'http://192.168.1.228:8083', title: 'FileBrowser' })
|
||||
store.open({ url: 'http://192.0.2.10:8083', title: 'FileBrowser' })
|
||||
|
||||
// Known port — routes to session (panel mode by default)
|
||||
expect(store.isOpen).toBe(false)
|
||||
@@ -344,17 +344,17 @@ describe('useAppLauncherStore', () => {
|
||||
const store = useAppLauncherStore()
|
||||
|
||||
// Unresolvable URL — falls through to iframe overlay
|
||||
store.open({ url: 'http://192.168.1.228:9999', title: 'Custom App' })
|
||||
store.open({ url: 'http://192.0.2.10:9999', title: 'Custom App' })
|
||||
|
||||
expect(store.isOpen).toBe(true)
|
||||
expect(store.url).toBe('http://192.168.1.228:9999')
|
||||
expect(store.url).toBe('http://192.0.2.10:9999')
|
||||
expect(store.title).toBe('Custom App')
|
||||
expect(mockWindowOpen).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('opens unknown different-host URL in iframe overlay', () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { origin: 'https://192.168.1.228', protocol: 'https:', hostname: '192.168.1.228' },
|
||||
value: { origin: 'https://192.0.2.10', protocol: 'https:', hostname: '192.0.2.10' },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
@@ -370,7 +370,7 @@ describe('useAppLauncherStore', () => {
|
||||
it('close resets state', () => {
|
||||
const store = useAppLauncherStore()
|
||||
// Use unknown URL to trigger iframe overlay
|
||||
store.open({ url: 'http://192.168.1.228:9999', title: 'Custom' })
|
||||
store.open({ url: 'http://192.0.2.10:9999', title: 'Custom' })
|
||||
|
||||
store.close()
|
||||
|
||||
@@ -385,7 +385,7 @@ describe('useAppLauncherStore', () => {
|
||||
const mockButton = { focus: vi.fn() } as unknown as HTMLElement
|
||||
Object.defineProperty(document, 'activeElement', { value: mockButton, configurable: true })
|
||||
|
||||
store.open({ url: 'http://192.168.1.228:9999', title: 'Custom' })
|
||||
store.open({ url: 'http://192.0.2.10:9999', title: 'Custom' })
|
||||
store.close()
|
||||
|
||||
expect(store.isOpen).toBe(false)
|
||||
|
||||
@@ -144,7 +144,7 @@ export const dummyApps: Record<string, PackageDataEntry> = {
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'lorabell.onion',
|
||||
'lan-address': 'http://192.168.1.166'
|
||||
'lan-address': 'http://192.0.2.13'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
<p class="text-sm text-white/60 mb-5">
|
||||
The URL should be of the form <span class="font-mono text-white/80">host[:port]/namespace</span>
|
||||
— for example <span class="font-mono text-white/80">ghcr.io/myorg</span> or
|
||||
<span class="font-mono text-white/80">192.168.1.50:3000/apps</span>. Registries are
|
||||
<span class="font-mono text-white/80">192.0.2.10:3000/apps</span>. Registries are
|
||||
added to the end of the list; use "Make primary" to reorder.
|
||||
</p>
|
||||
<form class="space-y-3" @submit.prevent="submitRegistry">
|
||||
|
||||
@@ -368,7 +368,7 @@ const backendPort = 5678
|
||||
function getLaunchUrl(app: BundledApp): string {
|
||||
// Prefer lan_address from backend (for apps with custom UIs)
|
||||
if (app.lan_address) {
|
||||
// Replace localhost so Launch works when browsing from another machine (e.g. 192.168.1.228)
|
||||
// Replace localhost so Launch works when browsing from another machine (e.g. a LAN address)
|
||||
let url = app.lan_address.replace(/localhost/i, currentHost.value)
|
||||
// LND UI (and other app UIs) need backend URL for live data (logs, getinfo proxy)
|
||||
if (app.id === 'lnd') {
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('AppSession mobile new-tab apps', () => {
|
||||
configurable: true,
|
||||
})
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { hostname: '192.168.1.228' },
|
||||
value: { hostname: '192.0.2.10' },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('Server network refresh states', () => {
|
||||
vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => {
|
||||
if (request.method === 'network.list-interfaces') {
|
||||
return Promise.resolve({
|
||||
interfaces: [{ name: 'eth0', type: 'ethernet', state: 'up', mac: '00:11:22:33:44:55', ipv4: ['192.168.1.10'] }],
|
||||
interfaces: [{ name: 'eth0', type: 'ethernet', state: 'up', mac: '00:11:22:33:44:55', ipv4: ['192.0.2.10'] }],
|
||||
})
|
||||
}
|
||||
if (request.method === 'network.diagnostics') {
|
||||
@@ -131,7 +131,7 @@ describe('Server network refresh states', () => {
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('eth0')
|
||||
expect(wrapper.text()).toContain('192.168.1.10')
|
||||
expect(wrapper.text()).toContain('192.0.2.10')
|
||||
|
||||
const pendingInterfaces = deferred<{ interfaces: [] }>()
|
||||
vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => {
|
||||
@@ -143,7 +143,7 @@ describe('Server network refresh states', () => {
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.text()).toContain('eth0')
|
||||
expect(wrapper.text()).toContain('192.168.1.10')
|
||||
expect(wrapper.text()).toContain('192.0.2.10')
|
||||
expect(wrapper.text()).toContain('Refreshing interfaces...')
|
||||
|
||||
pendingInterfaces.reject(new Error('offline'))
|
||||
@@ -151,7 +151,7 @@ describe('Server network refresh states', () => {
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('eth0')
|
||||
expect(wrapper.text()).toContain('192.168.1.10')
|
||||
expect(wrapper.text()).toContain('192.0.2.10')
|
||||
})
|
||||
|
||||
it('keeps Tor services visible while refresh is pending or fails', async () => {
|
||||
|
||||
@@ -52,39 +52,6 @@ export function resolvePackageKey(routeId: string): string {
|
||||
/** Apps that depend on Bitcoin being synced */
|
||||
export const BITCOIN_DEPENDENT_APPS = ['lnd', 'electrumx', 'electrs', 'mempool-electrs', 'btcpay-server', 'btcpayserver']
|
||||
|
||||
/** App launch URLs for dev and prod environments */
|
||||
export const APP_URLS: Record<string, { dev: string; prod: string }> = {
|
||||
'lorabell': { dev: 'http://192.168.1.166', prod: 'http://192.168.1.166' },
|
||||
'atob': { dev: 'http://localhost:8102', prod: 'https://app.atobitcoin.io' },
|
||||
'k484': { dev: 'http://localhost:8103', prod: 'http://localhost:8103' },
|
||||
'bitcoin': { dev: 'http://localhost:8332', prod: 'http://localhost:8332' },
|
||||
'btcpay-server': { dev: 'http://localhost:23000', prod: 'http://localhost:23000' },
|
||||
'homeassistant': { dev: 'http://localhost:8123', prod: 'http://localhost:8123' },
|
||||
'grafana': { dev: 'http://localhost:3000', prod: 'http://localhost:3000' },
|
||||
'endurain': { dev: 'http://localhost:8080', prod: 'http://localhost:8080' },
|
||||
'fedimint': { dev: 'http://localhost:8175', prod: 'http://192.168.1.228:8175' },
|
||||
'fedimint-gateway': { dev: 'http://localhost:8176', prod: 'http://192.168.1.228:8176' },
|
||||
'morphos-server': { dev: 'http://localhost:8081', prod: 'http://localhost:8081' },
|
||||
'lightning-stack': { dev: 'http://localhost:9735', prod: 'http://localhost:9735' },
|
||||
'mempool': { dev: 'http://localhost:4080', prod: 'http://localhost:4080' },
|
||||
'ollama': { dev: 'http://localhost:11434', prod: 'http://localhost:11434' },
|
||||
'searxng': { dev: 'http://localhost:8888', prod: 'http://localhost:8888' },
|
||||
'nextcloud': { dev: 'http://localhost:8085', prod: 'http://localhost:8085' },
|
||||
'vaultwarden': { dev: 'http://localhost:8082', prod: 'http://localhost:8082' },
|
||||
'jellyfin': { dev: 'http://localhost:8096', prod: 'http://localhost:8096' },
|
||||
'photoprism': { dev: 'http://localhost:2342', prod: 'http://localhost:2342' },
|
||||
'immich': { dev: 'http://localhost:2283', prod: 'http://localhost:2283' },
|
||||
'filebrowser': { dev: 'http://localhost:8083', prod: 'http://localhost:8083' },
|
||||
'nginx-proxy-manager': { dev: 'http://localhost:8081', prod: 'http://localhost:8081' },
|
||||
'gitea': { dev: 'http://localhost:3001', prod: 'http://localhost:3001' },
|
||||
'portainer': { dev: 'http://localhost:9000', prod: 'http://localhost:9000' },
|
||||
'uptime-kuma': { dev: 'http://localhost:3002', prod: 'http://localhost:3002' },
|
||||
'tailscale': { dev: 'http://localhost:8240', prod: 'http://localhost:8240' },
|
||||
'lnd': { dev: 'http://localhost:18083', prod: 'http://localhost:18083' },
|
||||
'bitcoin-knots': { dev: 'http://localhost:8334', prod: 'http://localhost:8334' },
|
||||
'botfights': { dev: 'http://localhost:9100', prod: 'http://localhost:9100' },
|
||||
}
|
||||
|
||||
/** V3 onion addresses are 56+ chars + .onion. Placeholders like "btcpay.onion" are not real. */
|
||||
export function isRealOnionAddress(addr: string | undefined): boolean {
|
||||
return !!(addr && addr.endsWith('.onion') && addr.length >= 60 && addr.length <= 70)
|
||||
|
||||
@@ -27,8 +27,8 @@ describe('appOrigin', () => {
|
||||
})
|
||||
|
||||
it('keeps the hostname the user actually typed, not a fixed name', () => {
|
||||
setLocation('https:', '100.69.68.39')
|
||||
expect(appOrigin(3000)).toBe('https://100.69.68.39:3000')
|
||||
setLocation('https:', '100.64.0.5')
|
||||
expect(appOrigin(3000)).toBe('https://100.64.0.5:3000')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -16,19 +16,19 @@ describe('appSessionConfig', () => {
|
||||
|
||||
it('resolves direct app ports against the current browser host', () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { hostname: '192.168.1.228' },
|
||||
value: { hostname: '192.0.2.10' },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
expect(resolveAppUrl('mempool')).toBe('http://192.168.1.228:4080')
|
||||
expect(resolveAppUrl('indeedhub')).toBe('http://192.168.1.228:7778')
|
||||
expect(resolveAppUrl('botfights')).toBe('http://192.168.1.228:9100')
|
||||
expect(resolveAppUrl('mempool')).toBe('http://192.0.2.10:4080')
|
||||
expect(resolveAppUrl('indeedhub')).toBe('http://192.0.2.10:7778')
|
||||
expect(resolveAppUrl('botfights')).toBe('http://192.0.2.10:9100')
|
||||
})
|
||||
|
||||
it('uses manifest-generated launch ports for apps outside the manual override list', () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { hostname: '192.168.1.228' },
|
||||
value: { hostname: '192.0.2.10' },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
@@ -36,12 +36,12 @@ describe('appSessionConfig', () => {
|
||||
// did-wallet's manifest publishes host port 8088 (apps/did-wallet/
|
||||
// manifest.yml) — assert against the manifest-generated value, which is
|
||||
// exactly what this test exists to protect.
|
||||
expect(resolveAppUrl('did-wallet')).toBe('http://192.168.1.228:8088')
|
||||
expect(resolveAppUrl('did-wallet')).toBe('http://192.0.2.10:8088')
|
||||
})
|
||||
|
||||
it('does not treat service-only tcp ports as web launch surfaces', () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { hostname: '192.168.1.228' },
|
||||
value: { hostname: '192.0.2.10' },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
@@ -51,21 +51,21 @@ describe('appSessionConfig', () => {
|
||||
|
||||
it('keeps NetBird on the unified dashboard proxy port', () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { hostname: '192.168.1.228' },
|
||||
value: { hostname: '192.0.2.10' },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
expect(resolveAppUrl('netbird', undefined, 'http://localhost:8086')).toBe('http://192.168.1.228:8087')
|
||||
expect(resolveAppUrl('netbird', undefined, 'http://localhost:8086')).toBe('http://192.0.2.10:8087')
|
||||
})
|
||||
|
||||
it('uses backend runtime URLs for apps with dynamic launch surfaces', () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { hostname: '192.168.1.228' },
|
||||
value: { hostname: '192.0.2.10' },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
expect(resolveAppUrl('filebrowser', undefined, 'http://localhost:18083')).toBe('http://192.168.1.228:18083')
|
||||
expect(resolveAppUrl('filebrowser', undefined, 'http://localhost:18083')).toBe('http://192.0.2.10:18083')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('AppIconGrid', () => {
|
||||
configurable: true,
|
||||
})
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { hostname: '192.168.1.198' },
|
||||
value: { hostname: '192.0.2.11' },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
@@ -112,7 +112,7 @@ describe('AppIconGrid', () => {
|
||||
await flushPromises()
|
||||
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'http://192.168.1.198:3001',
|
||||
'http://192.0.2.11:3001',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
|
||||
@@ -91,19 +91,19 @@ describe('fleet data helpers', () => {
|
||||
node_id: 'abcdef123456',
|
||||
node_name: 'Kitchen Node',
|
||||
hostname: 'kitchen-node',
|
||||
server_url: 'https://192.168.1.20',
|
||||
server_url: 'https://192.0.2.20',
|
||||
})
|
||||
const hostOnly = normalizeFleetNode({
|
||||
node_id: '123456abcdef',
|
||||
hostname: 'workshop-node',
|
||||
server_url: 'https://192.168.1.21',
|
||||
server_url: 'https://192.0.2.21',
|
||||
})
|
||||
const idOnly = normalizeFleetNode({ node_id: 'feedfacecafebeef' })
|
||||
|
||||
expect(fleetNodeDisplayName(named)).toBe('Kitchen Node')
|
||||
expect(fleetNodeSubtitle(named)).toBe('kitchen-node')
|
||||
expect(fleetNodeDisplayName(hostOnly)).toBe('workshop-node')
|
||||
expect(fleetNodeSubtitle(hostOnly)).toBe('https://192.168.1.21')
|
||||
expect(fleetNodeSubtitle(hostOnly)).toBe('https://192.0.2.21')
|
||||
expect(fleetNodeDisplayName(idOnly)).toBe('feedface')
|
||||
expect(fleetNodeSubtitle(idOnly)).toBe('feedfacecafebeef')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user