From 753da65171d4f77468293f5cb8b546621a07eace Mon Sep 17 00:00:00 2001 From: Barak Einav Date: Mon, 15 Dec 2025 14:38:16 +0000 Subject: [PATCH 1/8] feat(webui): add support for spesific IPs for port binding --- vmm/ui/src/components/PortMappingEditor.ts | 62 +++++++++++++++++++--- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/vmm/ui/src/components/PortMappingEditor.ts b/vmm/ui/src/components/PortMappingEditor.ts index 8658b8bb..1a1a7058 100644 --- a/vmm/ui/src/components/PortMappingEditor.ts +++ b/vmm/ui/src/components/PortMappingEditor.ts @@ -3,9 +3,11 @@ type PortEntry = { protocol: string; - host_address: string; + host_address: string; // Actual address sent to backend + host_address_mode?: string; // "local" | "public" | "custom" host_port: number | null; vm_port: number | null; + custom_ip?: string; // User-entered IP for custom mode }; type ComponentInstance = { @@ -24,33 +26,81 @@ const PortMappingEditorComponent = {
+ + - + + + + + + + + - + + +
- + + +
`, + methods: { addPort(this: ComponentInstance) { this.ports.push({ protocol: 'tcp', host_address: '127.0.0.1', + host_address_mode: 'local', + custom_ip: '', host_port: null, vm_port: null, }); }, + removePort(this: ComponentInstance, index: number) { this.ports.splice(index, 1); }, + + // Called when switching between Local / Public / Custom + onModeChange(port: PortEntry) { + if (port.host_address_mode === 'local') { + port.host_address = '127.0.0.1'; + } + else if (port.host_address_mode === 'public') { + port.host_address = '0.0.0.0'; + } + else if (port.host_address_mode === 'custom') { + port.host_address = port.custom_ip || ''; + } + }, + + // Called when user types a custom IP + onCustomIPChange(port: PortEntry) { + if (port.host_address_mode === 'custom') { + port.host_address = port.custom_ip || ''; + } + }, }, }; From 4a2b0a581a864bd3e3eda8a330dc7df77add3986 Mon Sep 17 00:00:00 2001 From: Barak Einav Date: Tue, 6 Jan 2026 14:53:07 +0000 Subject: [PATCH 2/8] add support for dedicated IP --- vmm/ui/src/components/PortMappingEditor.ts | 71 ++++++--- vmm/ui/src/styles/main.css | 2 +- vmm/ui/src/templates/app.html | 165 ++++++++++----------- 3 files changed, 129 insertions(+), 109 deletions(-) diff --git a/vmm/ui/src/components/PortMappingEditor.ts b/vmm/ui/src/components/PortMappingEditor.ts index 1a1a7058..2d5231b4 100644 --- a/vmm/ui/src/components/PortMappingEditor.ts +++ b/vmm/ui/src/components/PortMappingEditor.ts @@ -10,37 +10,49 @@ type PortEntry = { custom_ip?: string; // User-entered IP for custom mode }; +// ... keep your types as-is ... + type ComponentInstance = { ports: PortEntry[]; + normalizePort: (p: PortEntry) => void; }; const PortMappingEditorComponent = { name: 'PortMappingEditor', props: { + ports: { type: Array, required: true }, + }, + + // ✅ NEW: normalize on initial load + created(this: ComponentInstance) { + this.ports.forEach((p) => this.normalizePort(p)); + }, + + // ✅ NEW: normalize again if parent replaces ports array (e.g. after refresh) + watch: { ports: { - type: Array, - required: true, + deep: true, + handler(this: ComponentInstance, newPorts: PortEntry[]) { + newPorts.forEach((p) => this.normalizePort(p)); + }, }, }, + template: /* html */ `
- - - - - - - - +
- - - +
`, methods: { + // ✅ NEW: derives mode + custom_ip from host_address when editing existing VMs + normalizePort(this: ComponentInstance, port: PortEntry) { + // If mode already set, keep it (don’t fight the user while editing) + if (port.host_address_mode) return; + + if (port.host_address === '127.0.0.1') { + port.host_address_mode = 'local'; + port.custom_ip = ''; + } else if (port.host_address === '0.0.0.0') { + port.host_address_mode = 'public'; + port.custom_ip = ''; + } else { + port.host_address_mode = 'custom'; + port.custom_ip = port.host_address; // show dedicated IP in input + } + }, + addPort(this: ComponentInstance) { this.ports.push({ protocol: 'tcp', @@ -82,20 +102,25 @@ const PortMappingEditorComponent = { this.ports.splice(index, 1); }, - // Called when switching between Local / Public / Custom onModeChange(port: PortEntry) { if (port.host_address_mode === 'local') { port.host_address = '127.0.0.1'; - } - else if (port.host_address_mode === 'public') { + port.custom_ip = ''; + } else if (port.host_address_mode === 'public') { port.host_address = '0.0.0.0'; - } - else if (port.host_address_mode === 'custom') { + port.custom_ip = ''; + } else if (port.host_address_mode === 'custom') { + // if coming from existing custom, keep it; otherwise start empty + if (!port.custom_ip || port.custom_ip === '') { + // if host_address already contains a non-standard IP, reuse it + if (port.host_address !== '127.0.0.1' && port.host_address !== '0.0.0.0') { + port.custom_ip = port.host_address; + } + } port.host_address = port.custom_ip || ''; } }, - // Called when user types a custom IP onCustomIPChange(port: PortEntry) { if (port.host_address_mode === 'custom') { port.host_address = port.custom_ip || ''; diff --git a/vmm/ui/src/styles/main.css b/vmm/ui/src/styles/main.css index 45b267a9..54def8a5 100644 --- a/vmm/ui/src/styles/main.css +++ b/vmm/ui/src/styles/main.css @@ -1522,7 +1522,7 @@ h1, h2, h3, h4, h5, h6 { } .port-row { - grid-template-columns: 90px 100px 120px 120px 100px; + grid-template-columns: 90px 100px 120px 120px 160px 100px; } .gpu-config-items { diff --git a/vmm/ui/src/templates/app.html b/vmm/ui/src/templates/app.html index b55550fa..c7775ca0 100644 --- a/vmm/ui/src/templates/app.html +++ b/vmm/ui/src/templates/app.html @@ -18,41 +18,43 @@

dstack-vmm

- + - + - +