From 9ef18ff0551913d5cdcf813be58919ec7a805aff Mon Sep 17 00:00:00 2001 From: Paul Donald Date: Thu, 14 May 2026 15:00:45 +0300 Subject: [PATCH] luci-app-dockerman: network aliases feature Add network aliases to container view/create. Also init with ipv6 settings. Signed-off-by: Serhii Ivanov --- .../resources/view/dockerman/container.js | 32 ++++++++++++++++--- .../resources/view/dockerman/container_new.js | 32 +++++++++++++------ 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js b/applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js index 88c14b0d5e..80ce6863a4 100644 --- a/applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js +++ b/applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js @@ -308,6 +308,7 @@ return dm2.dv.extend({ DNSNames: net?.DNSNames || '', IPv4Address: net?.IPAMConfig?.IPv4Address || net?.IPAddress || '', IPv6Address: net?.IPAMConfig?.IPv6Address || '', + Aliases: net?.Aliases || '', }); } @@ -582,6 +583,8 @@ return dm2.dv.extend({ o = ss.option(form.DummyValue, 'DNSNames', _('DNS Names')); + o = ss.option(form.DummyValue, 'Aliases', _('Aliases')); + ss.handleAdd = function(ev) { ev.preventDefault(); view.executeNetworkAction('connect', null, null, this_container); @@ -1884,7 +1887,7 @@ return dm2.dv.extend({ const ip4Input = E('input', { 'type': 'text', - 'id': 'network-ip', + 'id': 'network-ip4', 'class': 'cbi-input-text', 'placeholder': 'e.g., 172.18.0.5', 'style': 'width:100%; margin-top:5px;' @@ -1892,18 +1895,29 @@ return dm2.dv.extend({ const ip6Input = E('input', { 'type': 'text', - 'id': 'network-ip', + 'id': 'network-ip6', 'class': 'cbi-input-text', 'placeholder': 'e.g., 2001:db8:1::1', 'style': 'width:100%; margin-top:5px;' }); + const aliasesInput = E('input', { + 'type': 'text', + 'id': 'network-aliases', + 'class': 'cbi-input-text', + 'placeholder': 'e.g., database,db (comma-separated)', + 'style': 'width:100%; margin-top:5px;' + }); + const modalBody = E('div', { 'class': 'cbi-section' }, [ E('p', {}, _('Select network to connect:')), networkSelect, - E('label', { 'style': 'display:block; margin-top:10px;' }, _('IP Address (optional):')), + E('label', { 'style': 'display:block; margin-top:10px;' }, _('IPv4 Address (optional):')), ip4Input, + E('label', { 'style': 'display:block; margin-top:10px;' }, _('IPv6 Address (optional):')), ip6Input, + E('label', { 'style': 'display:block; margin-top:10px;' }, _('Aliases (optional):')), + aliasesInput, ]); ui.showModal(_('Connect Network'), [ @@ -1919,7 +1933,9 @@ return dm2.dv.extend({ 'click': () => { const selectedNetwork = networkSelect.value; const ip4Address = ip4Input.value || ''; - // const ip6Address = ip6Input.value || ''; + const ip6Address = ip6Input.value || ''; + const aliasesRaw = aliasesInput.value || ''; + const aliases = aliasesRaw.split(',').map(a => a.trim()).filter(Boolean); if (!selectedNetwork) { view.showNotification(_('Error'), [_('No network selected')], 5000, 'error'); @@ -1929,7 +1945,13 @@ return dm2.dv.extend({ ui.hideModal(); const body = { Container: this_container.Id }; - body.EndpointConfig = { IPAMConfig: { IPv4Address: ip4Address } }; //, IPv6Address: ip6Address || null + body.EndpointConfig = { + IPAMConfig: { + IPv4Address: ip4Address || null, + IPv6Address: ip6Address || null + }, + Aliases: aliases.length > 0 ? aliases : null + }; view.executeDockerAction( dm2.network_connect, diff --git a/applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js b/applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js index b919004766..726240eac1 100644 --- a/applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js +++ b/applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js @@ -73,7 +73,8 @@ return dm2.dv.extend({ const hostConfig = c.HostConfig || {}; const resolvedImage = resolveImageId(c.Image) || resolveImageId(c.Config?.Image) || c.Image || c.Config?.Image || ''; const builtInNetworks = new Set(['none', 'bridge', 'host']); - const [netnames, nets] = Object.entries(c.NetworkSettings?.Networks || {}); + // Object.entries returns [[name, obj], ...] — pick the first network + const [[firstName, firstNet] = []] = Object.entries(c.NetworkSettings?.Networks || {}); containerData.container = { name: c.Name?.substring(1) || '', @@ -82,20 +83,22 @@ return dm2.dv.extend({ image: resolvedImage, privileged: hostConfig.Privileged ? 1 : 0, restart_policy: hostConfig.RestartPolicy?.Name || 'unless-stopped', - network: (() => { - return (netnames && (netnames.length > 0)) ? netnames[0] : ''; + network: firstName || '', + network_aliases: (() => { + if (!firstName || builtInNetworks.has(firstName)) return ''; + return (firstNet?.Aliases || []).join(', '); })(), ipv4: (() => { - if (builtInNetworks.has(netnames[0])) return ''; - return (nets && (nets.length > 0)) ? nets[0]?.IPAddress || '' : ''; + if (!firstName || builtInNetworks.has(firstName)) return ''; + return firstNet?.IPAddress || ''; })(), ipv6: (() => { - if (builtInNetworks.has(netnames[0])) return ''; - return (nets && (nets.length > 0)) ? nets[0]?.GlobalIPv6Address || '' : ''; + if (!firstName || builtInNetworks.has(firstName)) return ''; + return firstNet?.GlobalIPv6Address || ''; })(), ipv6_lla: (() => { - if (builtInNetworks.has(netnames[0])) return ''; - return (nets && (nets.length > 0)) ? nets[0]?.LinkLocalIPv6Address || '' : ''; + if (!firstName || builtInNetworks.has(firstName)) return ''; + return firstNet?.LinkLocalIPv6Address || ''; })(), link: hostConfig.Links || [], dns: hostConfig.Dns || [], @@ -272,6 +275,11 @@ return dm2.dv.extend({ o.datatype = 'ip6ll'; o.validate = not_with_a_docker_net; + o = s.option(form.Value, 'network_aliases', _('Network Aliases')); + o.rmempty = true; + o.placeholder = 'database,db (CSV)'; + o.validate = not_with_a_docker_net; + o = s.option(form.DynamicList, 'link', _('Links with other containers')); o.rmempty = true; o.placeholder='container_name:alias'; @@ -762,6 +770,7 @@ return dm2.dv.extend({ const name = get('name'); // const pull = toBool(get('pull')); const network = get('network'); + const network_aliases = get('network_aliases'); const publish = get('publish'); const command = get('command'); // const publish_all = toBool(get('publish_all')); @@ -829,7 +838,10 @@ return dm2.dv.extend({ Sysctls: sysctl ? listToKv(sysctl) : undefined, }, NetworkingConfig: { - EndpointsConfig: { [network]: { IPAMConfig: { IPv4Address: get('ipv4') || null, IPv6Address: get('ipv6') || null } } }, + EndpointsConfig: { [network]: { + IPAMConfig: { IPv4Address: get('ipv4') || null, IPv6Address: get('ipv6') || null }, + Aliases: network_aliases ? network_aliases.split(',').map(a => a.trim()).filter(Boolean) : null + } }, } };