luci-app-dockerman: network aliases feature

Add network aliases to container view/create.
Also init with ipv6 settings.

Signed-off-by: Serhii Ivanov <icegood1980@gmail.com>
This commit is contained in:
Paul Donald
2026-05-14 15:00:45 +03:00
parent 145f7e1bce
commit 9ef18ff055
2 changed files with 49 additions and 15 deletions
@@ -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,
@@ -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
} },
}
};